Итак, я знаю, что команда try / catch добавляет некоторые накладные расходы и, следовательно, не является хорошим способом управления потоком процесса, но откуда эти накладные расходы и каково их реальное влияние?





Я не эксперт в языковых реализациях (так что отнеситесь к этому с недоверием), но я думаю, что одна из самых больших затрат - это раскручивание стека и сохранение его для трассировки стека. Я подозреваю, что это происходит только тогда, когда генерируется исключение (но я не знаю), и если это так, это будет приличная скрытая стоимость каждый раз, когда генерируется исключение ... так что это не похоже на то, что вы просто прыгаете с одного места в коде к другому много чего происходит.
Я не думаю, что это проблема, пока вы используете исключения для ИСКЛЮЧИТЕЛЬНОГО поведения (то есть не ваш типичный ожидаемый путь в программе).
Хммм - в комментариях не работает разметка. Чтобы повторить попытку - исключения предназначены для ошибок, а не для «исключительного поведения» или условий: blogs.msdn.com/kcwalina/archive/2008/07/17/…
@Windows программист Статистика / источник, пожалуйста?
По моему опыту, самые большие накладные расходы связаны с созданием исключения и его обработкой. Однажды я работал над проектом, в котором использовался код, подобный приведенному ниже, чтобы проверить, имеет ли кто-то право редактировать какой-либо объект. Этот метод HasRight () использовался повсюду на уровне представления и часто вызывался для сотен объектов.
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
Когда тестовая база данных наполнялась тестовыми данными, это приводило к очень заметному замедлению при открытии новых форм и т. д.
Поэтому я изменил его до следующего, что, согласно более поздним быстрым и грязным измерениям, примерно на 2 порядка быстрее:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
Короче говоря, использование исключений в нормальном потоке процесса примерно на два порядка медленнее, чем использование аналогичного потока процесса без исключений.
Почему вы хотите создать здесь исключение? Вы можете справиться с ситуацией отсутствия прав на месте.
@ThunderGr - вот что я изменил, сделав его на два порядка быстрее.
Я сделал запись в блоге на эту тему в прошлом году. Проверить это. Суть в том, что блок try почти не требует затрат, если не возникает исключений - а на моем ноутбуке исключение составляло около 36 мкс. Это может быть меньше, чем вы ожидали, но имейте в виду, что эти результаты находятся в неглубоком стеке. Кроме того, первые исключения очень медленные.
Мне не удалось связаться с вашим блогом (время ожидания соединения истекло; вы слишком часто используете try / catch? Хе-хе), но вы, похоже, спорите со спецификацией языка и некоторыми MVP MS, которые также писали блоги на эту тему, предоставление измерений, противоречащих вашему совету ... Я открыт для предположения, что проведенное мной исследование неверно, но мне нужно прочитать вашу запись в блоге, чтобы узнать, что в нем говорится.
В дополнение к сообщению в блоге @ Hafthor, вот еще одно сообщение в блоге с кодом, специально написанным для проверки различий в скорости и производительности. Согласно результатам, если исключение возникает даже в 5% случаев, код обработки исключений в целом работает в 100 раз медленнее, чем код обработки исключений. Статья специально нацелена на блок try-catch и методы tryparse(), но концепция та же.
Здесь следует отметить три момента:
Во-первых, наличие в вашем коде блоков try-catch практически не снижает производительности или вообще не снижает ее. Это не должно быть соображением, когда вы пытаетесь избежать их использования в вашем приложении. Падение производительности вступает в игру только при возникновении исключения.
Когда возникает исключение в дополнение к операциям разматывания стека и т.д., которые имеют место, о которых упоминали другие, вы должны знать, что целая куча вещей, связанных со средой выполнения / отражением, происходит для заполнения членов класса исключения, таких как трассировка стека объект и различные члены типов и т. д.
Я считаю, что это одна из причин, по которой общий совет, если вы собираетесь повторно генерировать исключение, состоит в том, чтобы просто throw;, а не генерировать исключение снова или создавать новое, поскольку в этих случаях вся эта информация стека собирается повторно, тогда как в простой бросок в нем все сохранилось.
Также: когда вы повторно генерируете исключение как «throw ex», вы теряете исходную трассировку стека и заменяете ее трассировкой CURRENT стека; редко то, что нужно. Если вы просто «выбросите», то исходная трассировка стека в исключении сохраняется.
@ Эдди или throw new Exception("Wrapping layer’s error", ex);
Не говоря уже о том, что если он находится внутри часто вызываемого метода, это может повлиять на общее поведение приложения.
Например, я считаю использование Int32.Parse плохой практикой в большинстве случаев, поскольку он генерирует исключения для чего-то, что в противном случае можно было бы легко поймать.
Итак, в заключение все, что здесь написано:
1) Используйте блоки try..catch для обнаружения неожиданных ошибок - почти без потери производительности.
2) Не используйте исключения для исключенных ошибок, если вы можете этого избежать.
Вы спрашиваете о накладных расходах при использовании try / catch / finally, когда исключения не генерируются, или о накладных расходах, связанных с использованием исключений для управления потоком процесса? Последнее в некоторой степени похоже на использование динамита для зажигания свечи на день рождения малыша, и связанные с этим накладные расходы относятся к следующим областям:
Вы можете ожидать дополнительных ошибок страницы из-за сгенерированного исключения, обращающегося к нерезидентному коду и данным, которые обычно не входят в рабочий набор вашего приложения.
оба вышеперечисленных элемента обычно обращаются к «холодному» коду и данным, поэтому вероятны жесткие сбои страницы, если у вас вообще нехватка памяти:
Что касается фактического влияния стоимости, она может сильно варьироваться в зависимости от того, что еще происходит в вашем коде в то время. У Джона Скита есть хорошее резюме здесь с некоторыми полезными ссылками. Я склонен согласиться с его утверждением, что если вы дойдете до того момента, когда исключения значительно ухудшат вашу производительность, у вас возникнут проблемы с точки зрения использования исключений, помимо производительности.
Намного проще писать, отлаживать и поддерживать код, свободный от сообщений об ошибках компилятора, предупреждений анализа кода и стандартных принятых исключений (особенно исключений, которые генерируются в одном месте и принимаются в другом). Поскольку это проще, код в среднем будет лучше написан и менее ошибочным.
Для меня этот программист и накладные расходы на качество являются основным аргументом против использования try-catch для потока процесса.
Накладные расходы компьютера на исключения незначительны по сравнению с ними и обычно незначительны с точки зрения способности приложения удовлетворять реальным требованиям к производительности.
@Ritchard T, почему? В сравнении программисту и накладные расходы на качество это незначительны.
Мне очень нравится Сообщение блога Хафтора, и, чтобы добавить свои два цента к этому обсуждению, я хотел бы сказать, что мне всегда было легко, чтобы СЛОЙ ДАННЫХ генерировал только один тип исключения (DataAccessException). Таким образом, мой БИЗНЕС-СЛОЙ знает, какого исключения ожидать, и улавливает его. Затем, в зависимости от дальнейших бизнес-правил (например, если мой бизнес-объект участвует в рабочем процессе и т. д.), Я могу создать новое исключение (BusinessObjectException) или продолжить без повторного / выброса.
Я бы сказал, не стесняйтесь использовать try..catch всякий раз, когда это необходимо, и используйте его с умом!
Например, этот метод участвует в рабочем процессе ...
Комментарии?
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
Дэвид, не могли бы вы заключить вызов DeleteGallery в блок try / catch?
Поскольку DeleteGallery является логической функцией, мне кажется, что создание исключения в ней бесполезно. Для этого потребуется, чтобы вызов DeleteGallery был заключен в блок try / catch. Мне кажется более значимым if (! DeleteGallery (theid)) {// handle}. в этом конкретном примере.
Я написал об этом статью некоторое время назад, потому что в то время многие спрашивали об этом. Вы можете найти его и тестовый код на http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx.
Результатом является то, что накладные расходы на блок try / catch незначительны, но настолько малы, что их следует игнорировать. Однако, если вы запускаете блоки try / catch в циклах, которые выполняются миллионы раз, вы можете подумать о перемещении блока за пределы цикла, если это возможно.
Ключевая проблема с производительностью блоков try / catch - это когда вы действительно перехватываете исключение. Это может добавить заметную задержку в ваше приложение. Конечно, когда что-то идет не так, большинство разработчиков (и множество пользователей) воспринимают паузу как исключение, которое вот-вот должно произойти! Ключевым моментом здесь является не использовать обработку исключений для обычных операций. Как следует из названия, они исключительны, и вы должны делать все возможное, чтобы их не выбросили. Вы не должны использовать их как часть ожидаемого потока программы, которая работает правильно.
Мы можем прочитать в «Прагматике языков программирования» Майкла Л. Скотта, что современные компиляторы не добавляют никаких накладных расходов в общем случае, это означает, что не происходит никаких исключений. Таким образом, каждая работа выполняется во время компиляции. Но когда исключение выбрасывается во время выполнения, компилятору необходимо выполнить двоичный поиск, чтобы найти правильное исключение, и это будет происходить для каждого нового выброса, который вы сделали.
Но исключения есть исключения, и эта стоимость вполне приемлема. Если вы попытаетесь выполнить обработку исключений без исключений и вместо этого использовать коды ошибок возврата, вероятно, вам понадобится оператор if для каждой подпрограммы, и это повлечет за собой действительно накладные расходы в реальном времени. Вы знаете, что оператор if преобразуется в несколько инструкций по сборке, которые будут выполняться каждый раз, когда вы входите в свои подпрограммы.
Извините за мой английский, надеюсь, что он вам поможет. Эта информация основана на процитированной книге; дополнительную информацию см. В главе 8.5 Обработка исключений.
Компилятор отсутствует во время выполнения. должен - это накладные расходы на блоки try / catch, чтобы среда CLR могла обрабатывать исключения. C# работает на .NET CLR (виртуальной машине). Мне кажется, что накладные расходы самого блока минимальны, когда нет исключения, но стоимость обработки исключения в среде CLR очень велика.
Давайте проанализируем одну из самых больших возможных затрат на блок try / catch, когда он используется там, где его не нужно использовать:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
А вот и без try / catch:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Не считая незначительного пробела, можно заметить, что эти два эквивалентных фрагмента кода имеют почти одинаковую длину в байтах. Последний содержит отступ на 4 байта меньше. Это плохо?
Чтобы добавить оскорбление к травме, ученик решает зациклить, в то время как ввод может быть проанализирован как int. Решение без try / catch может выглядеть примерно так:
while (int.TryParse(...))
{
...
}
Но как это выглядит при использовании try / catch?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
Блоки try / catch - это волшебный способ тратить впустую отступы, и мы до сих пор даже не знаем, почему это не удалось! Представьте, что чувствует человек, выполняющий отладку, когда код продолжает выполняться после серьезной логической ошибки, а не останавливается с красивой очевидной ошибкой исключения. Блоки try / catch - это проверка / санация данных для ленивого человека.
Одна из меньших затрат заключается в том, что блоки try / catch действительно отключают определенные оптимизации: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx. Думаю, это тоже положительный момент. Его можно использовать для отключения оптимизаций, которые в противном случае могли бы нанести вред безопасным, нормальным алгоритмам передачи сообщений для многопоточных приложений, а также для выявления возможных состояний гонки;) Это единственный сценарий, который я могу придумать для использования try / catch. Даже у этого есть альтернативы.
Я почти уверен, что TryParse пытается {int x = int.Parse ("xxx"); вернуть истину;} поймать {вернуть ложь; } внутри. Вопрос не в отступах, а только в производительности и накладных расходах.
@ThunderGr Или прочтите опубликованный мной новый ответ. Он содержит больше ссылок, одна из которых является анализом значительного повышения производительности, когда вы избегаете Int.Parse в пользу Int.TryParse.
Точнее: попробовать дешево, улов дешево, бросить дорого. Если вы избегаете попытки поймать, бросок по-прежнему стоит дорого.