Насколько сильно снижается производительность в C # при использовании блока try, throw и catch

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

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

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

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

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

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


person Noah    schedule 05.03.2009    source источник
comment
Показанный здесь блок catch добавляет дополнительную информацию к объекту исключения, что может оправдать его присутствие. Но если блокировка бесполезна, удалите ее.   -  person Qwertie    schedule 06.03.2009


Ответы (8)


Бросок (а не улов) стоит дорого.

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

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

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

Перебросить:

catch (SomeException) {
  throw;
}

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

catch (SomeException e) {
  throw e;
}

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

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

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

person Richard    schedule 05.03.2009
comment
Есть ли что-то полезное, включая регистрацию ошибки перед повторной выдачей, или об этом лучше позаботиться в другом месте (например, об ошибке приложения)? - person Nick; 05.03.2009
comment
Перелистывание не требует больших затрат, так как не требуется создавать объект Exception (что требует раскрутки стека). - person Christian Klauser; 05.03.2009
comment
Но разве они не раскручивают стек на каждом уровне? - person DevinB; 05.03.2009
comment
@devinb: В данном случае это так, поскольку они генерируют новые исключения. Если бы они просто использовали throw ;, они бы не раскручивали стек на каждом уровне. Это следует использовать при регистрации. - person configurator; 05.03.2009
comment
это не так, обработчик catch должен раскрутиться, чтобы получить данные, включая чтение метаданных для стека вызовов. Это должно происходить в каждом улове, иначе вы не сможете распечатать стек вызовов. - person gbjbaanb; 05.03.2009
comment
@configurator: Понятно, что все это нужно написать. Но в качестве первого шага небольшое изменение может заключаться в простом изменении catch (TclException e) {throw e;} на catch (TclException e) {throw;} - person Noah; 05.03.2009
comment
Когда throw передается экземпляру Exception, он добавляет к нему новую информацию. Программисты ошибочно предполагают, что throw может отличить новый экземпляр Exception от использованного экземпляра Exception. Поскольку никаких различий не делается, единственный способ повторить вызов - это вообще не передавать throw экземпляр Exception. - person Triynko; 14.04.2009

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

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

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

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

person James Curran    schedule 05.03.2009
comment
В примере, приведенном в вопросе, исходная информация не теряется, поскольку исходное исключение передается как аргумент innerException в конструктор TclRuntimeError. - person yfeldblum; 05.03.2009
comment
Это правда. Однако фактическое исключение скрыто на шести уровнях в стороне от исключения, брошенного пользователю. - person Christian Klauser; 05.03.2009
comment
Как правило, вы должны заключать исключения только в точки абстракции, и даже тогда только в том случае, если вы будете предоставлять дополнительную информацию об ошибке. Например, обертывание стороннего компонента и обертывание любых нечетных исключений (IndexOutOfBounds- ›KeyNotFound) - person Guvante; 05.03.2009
comment
Исключения не для вещей, которые НИКОГДА не происходят, а для редких ошибок. Они медленнее, чем обычный поток управления, но несколько исключений в секунду не вызывают проблем с производительностью (кроме случаев, когда вы работаете в отладчике). - person Qwertie; 06.03.2009

Согласно msdn: Советы и рекомендации по повышению производительности, вы можете использовать попытаться поймать без каких-либо проблем с производительностью до тех пор, пока не произойдет настоящий бросок.

person Avram    schedule 05.03.2009
comment
Вы избавили меня от многих споров с коллегами с помощью этой ссылки :) Вот прямая цитата из статьи, если вы не хотите утруждать себя ее проверкой: Поиск и разработка кода с тяжелыми исключениями может привести к достойная победа по производительности. Имейте в виду, что это не имеет ничего общего с блоками try / catch: вы несете расходы только тогда, когда генерируется фактическое исключение. Вы можете использовать столько блоков try / catch, сколько захотите. Использование исключений без всяких оснований приводит к потере производительности. Например, вам следует избегать таких вещей, как использование исключений для потока управления. - person kayleeFrye_onDeck; 16.01.2015
comment
@kayleeFrye_onDeck да, вы можете использовать try and catch без каких-либо проблем, как можно больше. единственная проблема, если сценарий throw используется в обычном потоке программы. - person Avram; 16.01.2015

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

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

person C. Dragon 76    schedule 05.03.2009
comment
Ясно, что все это нужно писать. Но в качестве первого шага, может ли небольшое улучшение быть таким же простым, как изменение catch (TclException e) {throw e;} на catch (TclException e) {throw;}, чтобы исключение могло всплыть в стеке? - person Noah; 05.03.2009
comment
Да, но это даже проще. Просто удалите весь блок catch (TclException e). Если вы не поймаете исключение, оно само всплывет в стеке вызовов. - person C. Dragon 76; 06.03.2009

Само по себе это не страшно, но за гнездованием стоит понаблюдать.

Приемлемый вариант использования выглядит следующим образом:

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

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

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

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

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

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

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

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

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

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

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

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

person Quibblesome    schedule 05.03.2009
comment
часто вы действительно заботитесь о производительности - на сервере, если вы генерируете исключение только в 1% случаев, но вы принимаете 100 вызовов в секунду, более 100 пользователей - это 100 исключений в секунду! Иногда это может серьезно сказаться на производительности. - person gbjbaanb; 05.03.2009

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

person Fry    schedule 05.03.2009
comment
Сам блок try практически не имеет накладных расходов. Это исключительный случай (бросок / ловля), который замедляет работу системы. - person Christian Klauser; 05.03.2009
comment
Правильно - в его примере дело было в том, что использование try / catch было медленнее, потому что они приводили к сгенерированному исключению, которого можно было бы избежать, используя проверку, а затем выполняя другое действие вместо непрерывного выброса. - person Fry; 05.03.2009

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

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

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

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

person Daniel Earwicker    schedule 05.03.2009

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

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

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


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


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


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

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

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

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

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

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

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

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

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

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

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

person gbjbaanb    schedule 05.03.2009
comment
Старайтесь их не использовать? Это довольно грубое искажение совета Брамме. - person Daniel Earwicker; 05.03.2009
comment
нет, это мой совет, и это обобщение, чтобы заставить людей задуматься о том, что они делают. Крис говорит использовать их, но очень экономно: ... Убедившись, что случаи ошибок чрезвычайно редки - person gbjbaanb; 05.03.2009
comment
Итак ... вы предлагаете проверять значения, а не просто использовать исключения волей-неволей, поэтому вместо того, чтобы пытаться не использовать исключения, ваш совет больше: Старайтесь не создавать исключения? - person Fry; 10.03.2009
comment
собственно, старайтесь не использовать исключения. Я не говорил, что старайтесь не использовать обработку исключений. - person gbjbaanb; 11.03.2009
comment
Я бы переформулировал "Исключения медленные", постарайтесь не использовать их. Код, злоупотребляющий исключениями, - отстой, не делайте этого. Не используйте исключения для нормального выполнения программы. Кого волнует скорость в действительно исключительных ситуациях? - person Daniel Daranas; 15.04.2009