Каковы реальные накладные расходы при использовании try / catch в C#?

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
94
0
24 400
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Ответ принят как подходящий

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

Я не думаю, что это проблема, пока вы используете исключения для ИСКЛЮЧИТЕЛЬНОГО поведения (то есть не ваш типичный ожидаемый путь в программе).

Точнее: попробовать дешево, улов дешево, бросить дорого. Если вы избегаете попытки поймать, бросок по-прежнему стоит дорого.

Windows programmer 23.10.2008 10:08

Хммм - в комментариях не работает разметка. Чтобы повторить попытку - исключения предназначены для ошибок, а не для «исключительного поведения» или условий: blogs.msdn.com/kcwalina/archive/2008/07/17/…

HTTP 410 24.10.2008 20:56

@Windows программист Статистика / источник, пожалуйста?

Kapé 10.04.2014 13:25

По моему опыту, самые большие накладные расходы связаны с созданием исключения и его обработкой. Однажды я работал над проектом, в котором использовался код, подобный приведенному ниже, чтобы проверить, имеет ли кто-то право редактировать какой-либо объект. Этот метод 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 30.09.2013 12:55

@ThunderGr - вот что я изменил, сделав его на два порядка быстрее.

Tobi 07.10.2013 22:26

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

Мне не удалось связаться с вашим блогом (время ожидания соединения истекло; вы слишком часто используете try / catch? Хе-хе), но вы, похоже, спорите со спецификацией языка и некоторыми MVP MS, которые также писали блоги на эту тему, предоставление измерений, противоречащих вашему совету ... Я открыт для предположения, что проведенное мной исследование неверно, но мне нужно прочитать вашу запись в блоге, чтобы узнать, что в нем говорится.

autistic 16.08.2017 00:15

В дополнение к сообщению в блоге @ Hafthor, вот еще одно сообщение в блоге с кодом, специально написанным для проверки различий в скорости и производительности. Согласно результатам, если исключение возникает даже в 5% случаев, код обработки исключений в целом работает в 100 раз медленнее, чем код обработки исключений. Статья специально нацелена на блок try-catch и методы tryparse(), но концепция та же.

user3810913 27.11.2017 02:48

Здесь следует отметить три момента:

  • Во-первых, наличие в вашем коде блоков try-catch практически не снижает производительности или вообще не снижает ее. Это не должно быть соображением, когда вы пытаетесь избежать их использования в вашем приложении. Падение производительности вступает в игру только при возникновении исключения.

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

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

Также: когда вы повторно генерируете исключение как «throw ex», вы теряете исходную трассировку стека и заменяете ее трассировкой CURRENT стека; редко то, что нужно. Если вы просто «выбросите», то исходная трассировка стека в исключении сохраняется.

Eddie 12.06.2009 22:35

@ Эдди или throw new Exception("Wrapping layer’s error", ex);

binki 10.02.2018 02:37

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

Итак, в заключение все, что здесь написано:
1) Используйте блоки try..catch для обнаружения неожиданных ошибок - почти без потери производительности.
2) Не используйте исключения для исключенных ошибок, если вы можете этого избежать.

Вы спрашиваете о накладных расходах при использовании try / catch / finally, когда исключения не генерируются, или о накладных расходах, связанных с использованием исключений для управления потоком процесса? Последнее в некоторой степени похоже на использование динамита для зажигания свечи на день рождения малыша, и связанные с этим накладные расходы относятся к следующим областям:

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

    • например, для генерации исключения потребуется, чтобы среда CLR находила расположение блоков finally и catch на основе текущего IP-адреса и возвращаемого IP-адреса каждого кадра до тех пор, пока исключение не будет обработано, плюс блок фильтра.
    • дополнительные затраты на строительство и разрешение имен для создания фреймов для диагностических целей, включая чтение метаданных и т. д.
    • оба вышеперечисленных элемента обычно обращаются к «холодному» коду и данным, поэтому вероятны жесткие сбои страницы, если у вас вообще нехватка памяти:

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

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

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

Для меня этот программист и накладные расходы на качество являются основным аргументом против использования try-catch для потока процесса.

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

@Ritchard T, почему? В сравнении программисту и накладные расходы на качество это незначительны.

ThunderGr 30.09.2013 13:03

Мне очень нравится Сообщение блога Хафтора, и, чтобы добавить свои два цента к этому обсуждению, я хотел бы сказать, что мне всегда было легко, чтобы СЛОЙ ДАННЫХ генерировал только один тип исключения (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?

Rob 28.04.2009 12:44

Поскольку DeleteGallery является логической функцией, мне кажется, что создание исключения в ней бесполезно. Для этого потребуется, чтобы вызов DeleteGallery был заключен в блок try / catch. Мне кажется более значимым if (! DeleteGallery (theid)) {// handle}. в этом конкретном примере.

ThunderGr 30.09.2013 13:00

Я написал об этом статью некоторое время назад, потому что в то время многие спрашивали об этом. Вы можете найти его и тестовый код на http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx.

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

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

Мы можем прочитать в «Прагматике языков программирования» Майкла Л. Скотта, что современные компиляторы не добавляют никаких накладных расходов в общем случае, это означает, что не происходит никаких исключений. Таким образом, каждая работа выполняется во время компиляции. Но когда исключение выбрасывается во время выполнения, компилятору необходимо выполнить двоичный поиск, чтобы найти правильное исключение, и это будет происходить для каждого нового выброса, который вы сделали.

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

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

Компилятор отсутствует во время выполнения. должен - это накладные расходы на блоки try / catch, чтобы среда CLR могла обрабатывать исключения. C# работает на .NET CLR (виртуальной машине). Мне кажется, что накладные расходы самого блока минимальны, когда нет исключения, но стоимость обработки исключения в среде CLR очень велика.

ThunderGr 30.09.2013 13:06

Давайте проанализируем одну из самых больших возможных затрат на блок 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 30.09.2013 13:10

@ThunderGr Или прочтите опубликованный мной новый ответ. Он содержит больше ссылок, одна из которых является анализом значительного повышения производительности, когда вы избегаете Int.Parse в пользу Int.TryParse.

autistic 15.08.2017 23:54

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