Я все время вижу, как люди говорят, что исключения - это медленно, но я никогда не вижу никаких доказательств. Поэтому вместо того, чтобы спрашивать, есть ли они, я спрошу, как исключения работают за кулисами, чтобы я мог принимать решения о том, когда их использовать и медленные ли они.
Насколько я знаю, исключения - это то же самое, что и возврат кучу раз, за исключением того, что он также проверяет после каждого возврата, нужно ли ему сделать еще один или остановиться. Как он проверяет, когда перестать возвращаться? Я предполагаю, что есть второй стек, который содержит тип исключения и местоположение стека, затем он возвращается, пока не попадет туда. Я также предполагаю, что этот второй стек затрагивается только при броске и при каждой попытке / улове. AFAICT, реализующий подобное поведение с кодами возврата, займет такое же время. Но это всего лишь предположение, поэтому я хочу знать, что происходит на самом деле.
Как на самом деле работают исключения?
Ответы:
Вместо того чтобы гадать, я решил взглянуть на сгенерированный код с небольшим фрагментом кода C ++ и несколько старой установкой Linux.
Я скомпилировал его с помощью
g++ -m32 -W -Wall -O3 -save-temps -c
и посмотрел на созданный файл сборки._ZN11MyExceptionD1Ev
естьMyException::~MyException()
, поэтому компилятор решил, что ему нужна не встроенная копия деструктора.Сюрприз! В обычном пути кода нет никаких дополнительных инструкций. Компилятор вместо этого сгенерировал дополнительные блоки кода внесения исправлений, на которые ссылается таблица в конце функции (которая фактически помещается в отдельный раздел исполняемого файла). Вся работа делается за кулисами с помощью стандартной библиотеки, на основе этих таблиц (
_ZTI11MyException
естьtypeinfo for MyException
).Ладно, это не было для меня неожиданностью, я уже знал, как это делает компилятор. Продолжая сборку вывода:
Здесь мы видим код для выдачи исключения. Хотя дополнительных накладных расходов не было просто потому, что могло быть сгенерировано исключение, очевидно, что фактическое создание и перехват исключения связано с большими накладными расходами. Большая его часть скрыта внутри
__cxa_throw
, что должно:Сравните это со стоимостью простого возврата значения, и вы поймете, почему исключения следует использовать только для исключительных возвратов.
В завершение оставшаяся часть файла сборки:
Данные typeinfo.
Еще больше таблиц обработки исключений и дополнительная информация.
Итак, вывод, по крайней мере для GCC в Linux: стоимость - это дополнительное пространство (для обработчиков и таблиц), независимо от того, выбрасываются ли исключения, плюс дополнительные затраты на синтаксический анализ таблиц и выполнение обработчиков при возникновении исключения. Если вы используете исключения вместо кодов ошибок, и ошибка возникает редко, это может быть быстрее , поскольку у вас больше нет накладных расходов на тестирование ошибок.
Если вам нужна дополнительная информация, в частности, что делают все
__cxa_
функции, см. Исходную спецификацию, из которой они взяты:источник
В прежние времена медлительность в виде исключений была правдой.
В большинстве современных компиляторов это уже не так.
Примечание. Наличие исключений не означает, что мы также не используем коды ошибок. Если ошибка может быть обработана локально, используйте коды ошибок. Когда ошибки требуют большего контекста для исправления, используйте исключения: Я написал это гораздо более красноречиво здесь: Каковы принципы, которыми руководствуется ваша политика обработки исключений?
Стоимость кода обработки исключений, когда исключения не используются, практически равна нулю.
Когда генерируется исключение, уже выполняется некоторая работа.
Но вы должны сравнить это со стоимостью возврата кодов ошибок и их полной проверки до точки, где ошибка может быть обработана. И то, и другое требует больше времени на написание и поддержку.
Также есть одна хитрость для новичков:
хотя объекты Exception должны быть небольшими, некоторые люди помещают в них много чего. Тогда у вас есть затраты на копирование объекта исключения. Есть два решения:
На мой взгляд, я готов поспорить, что тот же код с исключениями либо более эффективен, либо, по крайней мере, сопоставим с кодом без исключений (но имеет весь дополнительный код для проверки результатов ошибок функции). Помните, что вы ничего не получаете бесплатно - компилятор генерирует код, который вы должны были написать в первую очередь, для проверки кодов ошибок (и обычно компилятор намного эффективнее, чем человек).
источник
Есть несколько способов реализовать исключения, но обычно они будут полагаться на некоторую базовую поддержку со стороны ОС. В Windows это структурированный механизм обработки исключений.
Подробно обсуждается Code Project: как компилятор C ++ реализует обработку исключений.
Накладные расходы на исключения возникают из-за того, что компилятор должен сгенерировать код, чтобы отслеживать, какие объекты должны быть уничтожены в каждом кадре стека (или, точнее, в области видимости), если исключение распространяется за пределы этой области. Если у функции нет локальных переменных в стеке, требующих вызова деструкторов, то она не должна иметь потери производительности в отношении обработки исключений.
Использование кода возврата может раскрутить только один уровень стека за раз, тогда как механизм обработки исключений может перепрыгнуть намного дальше вниз по стеку за одну операцию, если ему нечего делать в промежуточных кадрах стека.
источник
Мэтт Питрек написал отличную статью о структурированной обработке исключений Win32 . Хотя эта статья была первоначально написана в 1997 году, она применима и сегодня (но, конечно, применима только к Windows).
источник
В этой статье исследуется проблема и в основном делается вывод о том, что на практике исключения связаны с затратами времени выполнения, хотя стоимость довольно низка, если исключение не генерируется. Хорошая статья, рекомендую.
источник
Один мой друг написал несколько лет назад, как Visual C ++ обрабатывает исключения.
http://www.xyzw.de/c160.html
источник
Все хорошие ответы.
Кроме того, подумайте о том, насколько проще отлаживать код, который выполняет «проверки при условии» в виде ворот в верхней части методов, вместо того, чтобы позволять коду генерировать исключения.
Мой девиз - легко написать работающий код. Самое главное - написать код для следующего человека, который его посмотрит. В некоторых случаях это вы через 9 месяцев, и вы не хотите ругать свое имя!
источник