Под C# какой удар по производительности является блоком try, throw и catch

Прежде всего, отказ от ответственности: у меня есть опыт работы на других языках, но я все еще изучаю тонкости C#

К проблеме... Я смотрю на некоторый код, который использует блоки try/catch таким образом, который касается меня. Когда вызывается процедура синтаксического анализа, а не возвращается код ошибки, программист использует следующую логику

catch (TclException e) {
  throw new TclRuntimeError("unexpected TclException: " + e.Message,e);
}  

Это ловит вызывающий, который выдает ту же ошибку...
... который пойман вызывающим абонентом, который выдает ту же ошибку...
..... который ловит звонящий, который выбрасывает ту же ошибку...

резервное копирование около 6 уровней.

Правильно ли я считаю, что все эти блоки catch/throw вызывают проблемы с производительностью, или это разумная реализация в C#?

8 ответов

Решение

Бросить (а не поймать) дорого.

Не вставляйте блок catch, если вы не собираетесь делать что-то полезное (то есть конвертировать в более полезное исключение, обрабатывать ошибку).

Просто выбросить исключение (оператор throw без аргумента) или, что еще хуже, бросить тот же объект, что и только что перехваченный, - определенно неправильная вещь.

РЕДАКТИРОВАТЬ: Чтобы избежать двусмысленности:

Rethrow:

catch (SomeException) {
  throw;
}

Создайте исключение из предыдущего объекта исключения, где все предоставленное состояние времени выполнения (особенно трассировка стека) перезаписывается:

catch (SomeException e) {
  throw e;
}

Последний случай - бессмысленный способ выбросить информацию об исключении. и без чего-либо, предшествующего броску в блоке catch, вдвойне бессмысленно. Может быть и хуже

catch (SomeException e) {
  throw new SomeException(e.Message);
}

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

Это плохой дизайн на любом языке.

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

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

Также обратите внимание, что исключения предназначены для исключительных случаев. То, что никогда не должно случиться. Они не должны использоваться вместо обычной проверки достоверности (т. Е. Не перехватывать исключение деления на ноль; проверьте, не равен ли делитель нулю заранее).

Согласно msdn:Performance Tips and Tricks вы можете использовать попробовать и поймать без проблем с производительностью, пока не произойдет настоящий бросок.

Это не страшно само по себе, но нужно действительно наблюдать за гнездовьем.

Допустимый вариант использования:

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

catch(IOException ex)
{
    throw new PlatformException("some additional context", ex);
}

Теперь это позволяет потребителю сделать:

try
{
    component.TryThing();
}
catch(PlatformException ex)
{
   // handle error
}

Да, я знаю, что некоторые люди скажут, но потребитель должен поймать IOException, но это зависит от того, насколько абстрактным является фактически потребляющий код. Что если Impl что-то сохраняет на диск, а у потребителя нет веской причины полагать, что его работа коснется диска? В таком случае не имеет смысла помещать обработку этого исключения в потребляющий код.

Чего мы обычно пытаемся избежать, используя этот шаблон, так это размещаем обработчик исключений "ловить все" в коде бизнес-логики, потому что мы хотим выяснить все возможные типы исключений, поскольку они могут привести к более фундаментальной проблеме, которая должна быть решена. исследовалась. Если мы не поймаем, он пузырится, попадает в обработчик уровня "верхний верх" и должен остановить приложение дальше. Это означает, что клиент сообщит об этом исключении, и вы получите возможность его изучить. Это важно, когда вы пытаетесь создать надежное программное обеспечение. Вам нужно найти все эти случаи ошибок и написать специальный код для их обработки.

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

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

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

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

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

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

Как правило, исключение является дорогостоящим в.NET. Просто иметь блок try/catch/finally - это не так. Итак, да, существующий код плох с точки зрения производительности, потому что когда он генерирует, он выбрасывает 5-6 раздутых исключений без добавления какого-либо значения, а просто позволяет исходному исключению естественным образом создавать 5-6 кадров стека.

Хуже того, существующий код действительно плох с точки зрения дизайна. Одним из основных преимуществ обработки исключений (по сравнению с возвратом кодов ошибок) является то, что вам не нужно проверять исключения / коды возврата везде (в стеке вызовов). Вам нужно только поймать их в небольшом количестве мест, которые вы на самом деле хотите обработать. Игнорирование исключения (в отличие от игнорирования кода возврата) не игнорирует и не скрывает проблему. Это просто означает, что он будет обрабатываться выше в стеке вызовов.

Try / Catch / Throw медленны - лучшей реализацией будет проверка значения перед его перехватом, но если вы не можете продолжить, лучше бросать и ловить только тогда, когда он считается. В противном случае проверка и регистрация более эффективны.

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

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

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

Таким образом, эти блоки try / catch потенциально имеют стоимость во время разработки - они делают программу более запутанной. Но во время выполнения они будут налагать значительные затраты только тогда, когда генерируется исключение, потому что передача исключения вверх по стеку стала более сложной.

Исключения медленные, старайтесь их не использовать.

Смотрите ответ, который я дал здесь.

По сути, Крис Брумме (который входил в команду CLR) сказал, что они реализованы как исключения SEH, так что вы получите большой удар, когда их выбросят, вы должны будете понести штрафы, когда они пробиваются сквозь стек ОС. Это действительно отличная статья, в которой подробно рассказывается о том, что происходит, когда вы бросаете исключение. например.:


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


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


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

Рассмотрим некоторые вещи, которые происходят, когда вы генерируете исключение:

  • Получите трассировку стека, интерпретируя метаданные, испускаемые компилятором, чтобы привести наш стек в порядок.

  • Пройдите через цепочку обработчиков вверх по стеку, вызывая каждый обработчик дважды.

  • Компенсировать несоответствия между SEH, C++ и управляемыми исключениями.

  • Выделите управляемый экземпляр Exception и запустите его конструктор. Скорее всего, это связано с поиском ресурсов для различных сообщений об ошибках.

  • Вероятно, отправиться в путешествие по ядру ОС. Часто беру аппаратное исключение.

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

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

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

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

Другие вопросы по тегам