Исключения в C++ очень медленно


Я смотрел систематическая обработка ошибок в C++Андрей Александреску Он утверждает, что Exceptions in C++ очень очень медленно.

Я хочу знать, это все еще верно для C++98

7 72
c++

7 ответов:

основная модель, используемая сегодня для исключений (Itanium ABI, VC++ 64 бит),-это исключения модели с нулевой стоимостью.

идея заключается в том, что вместо того, чтобы терять время, устанавливая охрану и явно проверяя наличие исключений везде, компилятор генерирует боковую таблицу, которая отображает любую точку, которая может вызвать исключение (счетчик программ) в список обработчиков. Когда возникает исключение, этот список консультируется, чтобы выбрать правильный обработчик (если таковой имеется) и стек раскрученный.

по сравнению с типичным if (error) стратегия:

  • модель нулевой стоимости, как следует из названия, является бесплатной, когда не происходит никаких исключений
  • это стоит около 10x / 20x an if когда возникает исключение

стоимость, однако, не тривиально измерить:

  • таблица стороны вообще холод, и таким образом извлечение его из памяти занимает много времени
  • определение права обработчик включает RTTI: многие дескрипторы RTTI для выборки, разбросанные по памяти и сложные операции для запуска (в основном a

когда вопрос был отправлен, я был на пути к врачу, с ожидающим такси, поэтому у меня было время только для короткого комментария. Но теперь, комментируя и повышая и понижая, я лучше добавлю свой собственный ответ. Даже если Матье уже довольно хорошо.


исключения, особенно медленно в C++, по сравнению с другими языками?

повторное требование

"я смотрел Систематическая Обработка Ошибок в C++Андрей Александреску он утверждает, что исключения в C++ очень очень медленно."

если это буквально то, что утверждает Андрей, то на этот раз он очень вводит в заблуждение, если не совсем неправильно. Для повышенных/исключений всегда медленно по сравнению с другими основными операциями в языке, независимо от языка программирования. Не только в C++ или в C++, чем в других языках, как якобы утверждает.

в общем, в основном, независимо от языка, две основные языковые функции, которые на порядок медленнее остальных, потому что они преобразуются в вызовы подпрограмм, которые обрабатывают сложные структуры данных, являются

  • бросать исключение, и

  • динамическое выделение памяти.

к счастью, в C++ часто можно избежать как в критическом по времени коде.

к сожалению нет такой вещи, как A Бесплатный Обед, даже если эффективность по умолчанию C++ подходит довольно близко. :- ) Для эффективности, полученной за счет избежания выбрасывания исключений и динамического выделения памяти, как правило, достигается путем кодирования на более низком уровне абстракции, используя C++ как просто "лучший C". А более низкая абстракция означает большую "сложность".

большая сложность означает больше времени, затрачиваемого на техническое обслуживание и мало или вообще никакой выгоды от повторного использования кода, которые являются реальными денежными затратами, даже если их трудно оценить или мера. Т. е. с помощью C++ можно, при желании, обменять некоторую эффективность программиста на эффективность выполнения. Решение о том, делать ли это, в значительной степени является инженерным и интуитивным решением, потому что на практике можно легко оценить и измерить только прибыль, а не стоимость.


существуют ли какие-либо объективные показатели производительности выброса исключений C++?

да, Международный комитет по стандартизации C++ опубликовал технический отчет о производительности C++ , TR18015.


что это значит что исключения являются "медленными"?

в основном это означает, что a throw может занять очень много времени™ по сравнению с, например,int задание, из-за поиска обработчика.

как TR18015 обсуждает в своем разделе 5.4 "исключения" есть две основные стратегии реализации обработки исключений,

  • подход, где каждая try-блок динамически устанавливает up exception catching, так что поиск по динамической цепочке обработчиков выполняется при возникновении исключения, и

  • подход, при котором компилятор создает статические таблицы поиска, которые используются для определения обработчика для создаваемого исключения.

первый очень гибкий и общий подход почти принудительный в 32-битных окнах, в то время как в 64-битной земле и в * nix-land второй гораздо более эффективный подход обычно используемый.

кроме того, как обсуждается в этом докладе, для каждого подхода существует три основные области, в которых обработка исключений влияет на эффективность:

  • tryблоки

  • обычные функции (возможности оптимизации), и

  • throw-выражения.

в основном, при подходе динамического обработчика (32-разрядная Windows) обработка исключений влияет на try блоки, в основном независимо от языка (потому что это вынуждено Windows' Структурированная Обработка Исключений схема), в то время как статический табличный подход имеет примерно нулевую стоимость для try-блоки. Обсуждение этого заняло бы гораздо больше места и исследований, чем практически для такого ответа. Итак, смотрите отчет для деталей.

к сожалению, отчет от 2006 года уже немного датирован концом 2012 года, и, насколько я знаю, нет ничего сопоставимого новее.

еще одна важная перспектива заключается в том, что влияние использование исключений по производительности сильно отличается от изолированной эффективности функций поддерживающего языка, потому что, как отмечается в отчете,

" при рассмотрении обработки исключений, она должна быть противопоставлена альтернативным способам работа с ошибками."

например:

  • расходы на техническое обслуживание должные к различному стили программирования (корректность)

  • резервный сайт вызова if проверка отказа по сравнению с централизованной try

  • проблемы с кэшированием (например, более короткий код может поместиться в кэше)

отчет имеет другой список аспектов для рассмотрения, но в любом случае единственный практический способ получить твердые факты об эффективности выполнения, вероятно, реализовать ту же программу с использованием исключения и не использовать исключения, в рамках решенного лимита на время разработки, причем с разработчиками знакомыми по каждому пути, а потом мера.


что является хорошим способом, чтобы избежать исключения?

достоверность почти всегда превосходит эффективность.

без исключений, следующее может легко произойти:

  1. некоторый код P предназначен для получения ресурса или вычисления некоторых информация.

  2. вызывающий код C должен был проверить на успех/неудачу, но не делает.

  3. несуществующий ресурс или неверная информация используется в коде после C, вызывая общий хаос.

основная проблема-это точка (2), где с обычным возвращает код схема вызывающий код C не заставляют проверять.

есть два основных подхода, которые заставляют такие проверка:

  • где P непосредственно вызывает исключение, когда он терпит неудачу.

  • где P возвращает объект, который C должен проверить перед использованием его основного значения (в противном случае исключение или завершение).

второй подход, насколько мне известно, впервые описан Бартон и Nackman в своей книге *научный и инженерный C++: введение с передовыми методами и примерами, где они ввели класс Fallow "возможного" результата функции. Аналогичный класс называется optional теперь предлагается библиотекой Boost. И вы можете легко реализовать Optional класс самостоятельно, используя std::vector как несущая значения для случая результата non-POD.

при первом подходе вызывающий код C не имеет выбора, кроме как использовать методы обработки исключений. Однако при втором подходе вызывающий код C может сам по себе решите, стоит ли это делать if проверка на основе или общая обработка исключений. Таким образом, второй подход поддерживает компромисс между эффективностью программиста и временем выполнения.


каково влияние различных стандартов C++, на производительность исключение?

"я хочу знать, это все еще верно для 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++ через обратные вызовы языка C. Эта поддержка эффективно ограничивает способ хранения текущего исключения. Однако, насколько я знаю, это не влияет на производительность, за исключением той степени, что в более новом коде обработка исключений может быть более легко использована с обеих сторон обратного вызова языка C.

Это зависит от компилятора.

но обратите внимание, что обработка исключений должна - как говорится в названии - быть исключением, а не правилом в вашем программном обеспечении. Когда у вас есть приложение, которое выдает так много исключений в секунду, что это влияет на производительность, и это по-прежнему считается нормальной работой, то вы лучше думать о том, чтобы делать все по-другому.

исключения-отличный способ сделать код более читаемым, убрав весь этот неуклюжий код обработки ошибок с пути, но как только они становятся частью нормального потока программы, им становится очень трудно следовать. Помните, что throw очень много goto catch в маскировке.

Да, но это не важно. Зачем?
Читать this:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx

В основном это говорит о том, что использование исключений, таких как Alexandrescu описано (50X замедление, потому что они используют catch как else) - это просто неправильно. Это, как говорится, для ppl, которые любят делать это так Я бы хотел, чтобы C++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. Также обратите внимание, что даже если исключения-то, что медленно... это не проблема, если вы не тратите много времени в этой части кода во время выполнения... Например, если плавающее деление медленное, и вы делаете его в 4 раза быстрее, это не имеет значения, если вы тратите 0,3% своего времени на разделение FP...

исключения не должны происходить. Если вы ожидаете, что они произойдут, вы делаете это неправильно.

то есть имхо причина, почему стандарт не устанавливает детали реализации (кроме того, это зависит от ОС обычно, в Win32 вызывается RaiseException). Потому что, если это произойдет, его не должно сильно волновать, насколько он медленный.

Как и в silico, его реализация зависит, но в целом исключения считаются медленными для любой реализации и не должны использоваться в коде с высокой производительностью.

EDIT: я не говорю, что не используйте их вообще, но для интенсивного кода производительности лучше избегать их.

вы никогда не можете утверждать о производительности, если вы не преобразуете код в сборку или не проверяете его.

вот что вы увидите: (быстрый жим)

код ошибки не чувствителен к проценту возникновения. Исключения имеют немного накладных расходов, пока они никогда не выбрасываются. Как только вы бросаете их, начинается страдание. В этом примере, он бросается на 0%, 1%, 10%, 50% и в 90% случаев.

вы видите много противоречивых мнений о них. Но в конце концов, разве исключения бывают медленными? Я не осуждаю. Просто наблюдайте за эталоном.

C++ exceptions performance benchmark