Под C #, какой удар по производительности - блок try, throw и catch - PullRequest
13 голосов
/ 05 марта 2009

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

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

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

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

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

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

Ответы [ 8 ]

14 голосов
/ 05 марта 2009

Согласно msdn: Советы и рекомендации по производительности вы можете использовать попытку и поймать без проблем с производительностью до момента реального броска .

14 голосов
/ 05 марта 2009

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

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

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

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

10 голосов
/ 05 марта 2009

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

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

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

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

Rethrow:

catch (SomeException) {
  throw;
}

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

catch (SomeException e) {
  throw e;
}

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

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

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

2 голосов
/ 05 марта 2009

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2 голосов
/ 05 марта 2009

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

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

1 голос
/ 05 марта 2009

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

0 голосов
/ 05 марта 2009

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

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

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


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


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


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

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

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

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

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

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

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

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

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

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

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

0 голосов
/ 05 марта 2009

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

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

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

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

...