Я смотрел Систематическая обработка ошибок на С++ - Андрей Александреску, он утверждает, что Exceptions in C++
очень медленно.
Я хочу знать, что это все еще верно для C++98
Я смотрел Систематическая обработка ошибок на С++ - Андрей Александреску, он утверждает, что Exceptions in C++
очень медленно.
Я хочу знать, что это все еще верно для C++98
Основная модель, используемая сегодня для исключений (Itanium ABI, VC++ 64 бит), - это исключения модели с нулевой стоимостью.
Идея состоит в том, что вместо того, чтобы терять время, устанавливая защиту и явно проверяя наличие исключений везде, компилятор генерирует вспомогательную таблицу, которая отображает любую точку, которая может выбросить исключение (счетчик программ), в список обработчиков. Когда выдается исключение, этот список используется для выбора правильного обработчика (если есть), и стек разворачивается.
По сравнению с типичной стратегией if (error)
:
if
когда происходит исключениеСтоимость, однако, не является тривиальной для измерения:
dynamic_cast
для каждого обработчика)Таким образом, в основном отсутствует кеш, и, следовательно, нетривиально по сравнению с чистым кодом процессора.
Примечание: для получения более подробной информации прочитайте отчет TR18015, глава 5.4 Обработка исключений (pdf)
Итак, да, исключения являются медленными на исключительном пути, но в остальном они быстрее, чем явные проверки (if
стратегия) в целом.
Примечание: Андрей Александреску, кажется, ставит под сомнение это "быстрее". Лично я видел, как все меняется в обоих направлениях: некоторые программы работают быстрее с исключениями, а другие быстрее с ветвями, поэтому в определенных условиях действительно наблюдается потеря оптимизируемости.
Это имеет значение?
Я бы сказал, что это не так. Программа должна быть написана с учетом читабельности, а не производительности (по крайней мере, не в качестве первого критерия). Исключения следует использовать, когда ожидается, что вызывающая сторона не может или не пожелает обработать сбой на месте и передать его в стек. Бонус: в С++ 11 исключения могут быть распределены между потоками с использованием стандартной библиотеки.
Это тонко, хотя, я утверждаю, что map::find
не должен выбрасывать, но я в порядке с map::find
возвращающим checked_ptr
который выбрасывает, если попытка разыменования завершается неудачей, потому что это null: в последнем случае, как в случае класс, который представил Александреску, вызывающий выбирает между явной проверкой и использованием исключений. Расширение возможностей вызывающего абонента без предоставления ему большей ответственности обычно является признаком хорошего дизайна.
Когда вопрос был опубликован, я направлялся к врачу с ожиданием такси, так что у меня было время для краткого комментария. Но теперь, когда я прокомментировал и проголосовал против, я бы лучше добавил свой собственный ответ. Даже если ответ Матье уже довольно хорош.
Ре претензия
"Я наблюдал за Системной обработкой ошибок в C++ - Андрей Александреску утверждает, что исключения в C++ очень и очень медленные".
Если это буквально то, что утверждает Андрей, то на этот раз он очень вводит в заблуждение, если не прямо ошибается. Для повышенных/брошенных исключений всегда происходит медленно по сравнению с другими основными операциями в языке, независимо от языка программирования. Не только в C++ или больше в C++, чем в других языках, как указывается в заявлении.
В целом, в основном независимо от языка, две базовые языковые функции, которые на несколько порядков медленнее остальных, поскольку они преобразуются в вызовы подпрограмм, которые обрабатывают сложные структуры данных,
исключение, и
динамическое распределение памяти.
К счастью, в C++ часто можно избежать обоих в критичном по времени коде.
К сожалению, такого бесплатного обеда не бывает, даже если эффективность по умолчанию C++ довольно близка. :-) Поскольку эффективность достигается за счет исключения бросков исключений и динамического выделения памяти, обычно достигается путем кодирования на более низком уровне абстракции, используя C++ как просто "лучший C". А более низкая абстракция означает большую "сложность".
Большая сложность означает больше времени, затрачиваемого на обслуживание, и небольшую или нулевую выгоду от повторного использования кода, которые представляют собой реальные денежные затраты, даже если их трудно оценить или измерить. То есть, с C++ можно, при желании, обменять некоторую эффективность программиста на эффективность исполнения. Независимо от того, делать ли это, во многом является инженерным и интуитивным решением, поскольку на практике легко оценить и измерить только выигрыш, а не стоимость.
Да, международный комитет по стандартизации C++ опубликовал Технический отчет о работе C++, TR18015.
В основном это означает, что throw
может занять очень долгое время по сравнению, например, с присваиванием int
, из-за поиска обработчика.
Как поясняет TR18015 в разделе 5.4 "Исключения", существуют две основные стратегии реализации обработки исключений:
подход, при котором каждая try
-block динамически устанавливает перехват исключений, так что поиск динамической цепочки обработчиков выполняется при возникновении исключения, и
подход, при котором компилятор генерирует статические справочные таблицы, которые используются для определения обработчика для брошенного исключения.
Первый очень гибкий и общий подход почти обязателен в 32-битной Windows, в то время как в 64-битной земле и в * nix-land обычно используется второй, гораздо более эффективный подход.
Также, как указано в этом отчете, для каждого подхода есть три основные области, где обработка исключений влияет на эффективность:
try
-block s,
регулярные функции (возможности оптимизации) и
throw
-expressions.
В основном, при использовании подхода динамического обработчика (32-битная Windows) обработка исключений оказывает влияние на блоки try
, в основном независимо от языка (поскольку это обусловлено схемой обработки структурированных исключений Windows), в то время как подход со статическими таблицами имеет примерно нулевую стоимость для try
-block s. Обсуждение этого заняло бы гораздо больше места и исследований, чем практический ответ SO. Итак, подробности смотрите в отчете.
К сожалению, отчет за 2006 год уже немного датирован концом 2012 года, и, насколько я знаю, нет ничего похожего, что новее.
Другая важная перспектива заключается в том, что влияние использования исключений на производительность сильно отличается от изолированной эффективности функций поддержки языков, поскольку, как отмечается в отчете,
"При рассмотрении обработки исключений ее следует противопоставить альтернативным способам устранения ошибок".
Например:
Затраты на обслуживание из-за разных стилей программирования (правильность)
Резервный сайт вызова, if
проверка на отказ или централизованная try
Проблемы с кешированием (например, более короткий код может поместиться в кеше)
В отчете есть другой список аспектов, которые необходимо рассмотреть, но в любом случае единственный практический способ получить достоверные факты об эффективности выполнения, вероятно, состоит в том, чтобы реализовать одну и ту же программу с использованием исключений, а не исключений, в рамках установленного ограничения времени разработки и с разработчиками. знакомы с каждым способом, а затем ИЗМЕРЕНИЕ.
Корректность почти всегда превосходит эффективность.
Без исключений легко может произойти следующее:
Некоторый код P предназначен для получения ресурса или вычисления некоторой информации.
Вызывающий код C должен был проверить на успех/неудачу, но это не так.
Несуществующий ресурс или неверная информация используется в коде после C, вызывая общий хаос.
Основной проблемой является пункт (2), где при обычной схеме кода возврата вызывающий код C не вынужден проверять.
Есть два основных подхода, которые делают такую проверку принудительной:
Где P напрямую генерирует исключение, когда оно терпит неудачу.
Где P возвращает объект, который C должен проверить перед использованием его основного значения (в противном случае это исключение или завершение).
Второй подход, AFAIK, был впервые описан Бартоном и Нэкманом в их книге * Scientific and Engineering C++: Введение с передовыми методами и примерами, где они представили класс, называемый Fallow
для "возможного" результата функции. Подобный класс под названием optional
теперь предлагается библиотекой Boost. И вы можете легко реализовать Optional
класс самостоятельно, используя std::vector
качестве носителя значения для случая не POD-результата.
При первом подходе у вызывающего кода C нет другого выбора, кроме как использовать методы обработки исключений. При втором подходе, однако, вызывающий код C может сам решить, стоит ли это делать, if
на основе проверки, или общие обработки исключений. Таким образом, второй подход поддерживает компромисс между эффективностью программирования и временем выполнения.
"Я хочу знать, верно ли это для C++ 98"
C++ 98 был первым стандартом C++. Для исключений была введена стандартная иерархия классов исключений (к сожалению, довольно несовершенная). Основное влияние на производительность оказала возможность спецификаций исключений (удалено в C++ 11), которые, тем не менее, никогда не были полностью реализованы основным компилятором Windows C++. Visual C++: Visual C++ принимает исключение C++ 98. синтаксис спецификации, но просто игнорирует спецификации исключений.
C++ 03 был просто техническим исправлением C++ 98. Единственным действительно новым в C++ 03 была инициализация значения. Который не имеет ничего общего с исключениями.
С C++ 11 стандартными спецификациями общего исключения были удалены и заменены на ключевое слово noexcept
.
Стандарт C++ 11 также добавил поддержку для хранения и переброса исключений, что отлично подходит для распространения исключений C++ по обратным вызовам языка Си. Эта поддержка эффективно ограничивает способ хранения текущего исключения. Однако, насколько мне известно, это не влияет на производительность, за исключением того, что в более новом коде обработка исключений может быть легче использована с обеих сторон обратного вызова языка Си.
Это зависит от компилятора.
GCC, например, был известен тем, что имел очень низкую производительность при обработке исключений, но за последние несколько лет это значительно улучшилось.
Но обратите внимание, что обработка исключений должна - как следует из названия - быть скорее исключением, чем правилом в вашем программном обеспечении. Когда у вас есть приложение, которое бросает так много исключений в секунду, что оно влияет на производительность, и это по-прежнему считается нормальной работой, тогда вам следует подумать о том, чтобы делать что-то по-другому.
Исключения - отличный способ сделать код более читаемым, избавив весь этот неуклюжий код обработки ошибок, но как только они станут частью обычного потока программы, им становится очень трудно следовать. Помните, что throw
в значительной степени скрывается goto catch
.
Вы никогда не сможете претендовать на производительность, если не конвертируете код в сборку или не тестируете его
Вот что вы видите: (быстрый стенд)
Код ошибки не чувствителен к проценту возникновения. Исключения имеют немного накладных расходов, если они никогда не выбрасываются. Как только вы бросаете их, начинается страдание. В этом примере он выбрасывается в 0%, 1%, 10%, 50% и 90% случаев. Когда исключения генерируются в 90% случаев, код работает в 8 раз медленнее, чем в случае, когда исключения генерируются в 10% случаев. Как видите, исключения действительно медленные. Не используйте их, если их часто бросают. Если в вашем приложении нет требований в реальном времени, не стесняйтесь их бросать, если они встречаются очень редко.
Вы видите много противоречивых мнений о них. Но, в конце концов, исключения являются медленными? Я не судья. Просто посмотрите на тест.
Да, но это не имеет значения.
Почему?
Прочтите это:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
В основном, это говорит о том, что использование исключений, таких как описанный Alexandrescu (50-кратное замедление, поскольку они используют catch
как else
), просто неверно.
Это было сказано для ppl, который любит это делать так
Я хочу, чтобы С++ 22:) добавил что-то вроде:
(обратите внимание, что это должен быть основной язык, поскольку он в основном генерирует код компилятора из существующего)
result = attempt<lexical_cast<int>>("12345"); //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
int x = result.get(); // or result.result;
}
else
{
// even possible to see what is the exception that would have happened in original function
switch (result.exception_type())
//...
}
P.S. также обратите внимание, что даже если исключения являются медленными... это не проблема, если вы не тратите много времени на эту часть кода во время выполнения... Например, если float-деление медленное, и вы делаете его в 4 раза быстрее это не имеет значения, если вы потратите 0,3% своего времени на то, чтобы сделать разделение FP...
Исключений не должно быть. Если вы ожидаете, что они произойдут, вы делаете это неправильно.
Именно по этой причине стандарт не устанавливает детали реализации (кроме того, обычно это зависит от ОС, в Win32 вызывается RaiseException). Потому что, если это произойдет, его не должно волновать, насколько это медленно.
Как и в Silico, его зависимость зависит от реализации, но в целом исключения считаются медленными для любой реализации и не должны использоваться в коде с высокой степенью производительности.
EDIT: Я не говорю, что не использую их вообще, а для кода с интенсивным усилением лучше избегать их.