Каковы реальные накладные расходы на try/catch в С#?

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

Ответ 1

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

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

Ответ 2

Три момента, чтобы сделать здесь:

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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();
}

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

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

Мне очень нравится Hafthor сообщение в блоге, и чтобы добавить мои два цента в эту дискуссию, я бы хотел сказать, что это всегда было легко для меня, чтобы DATA LAYER выбрал только один тип исключения (DataAccessException). Таким образом мой БИЗНЕС-СЛОЙ знает, какое исключение ожидать и ловит его. Затем, в зависимости от дальнейших бизнес-правил (например, если мой бизнес-объект участвует в рабочем процессе и т.д.), Я могу создать новое исключение (исключение BusinessObjectException) или продолжить без re/throwing.

Я бы сказал, не стесняйтесь использовать 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;
}

Ответ 10

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

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

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

Ответ 11

В отличие от общепринятых теорий, try/catch может иметь значительные последствия для производительности и что выбрано исключение!

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

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

Здесь также этот ответ, который показывает разницу между дизассемблированным кодом с использованием и без использования try/catch.

Кажется настолько очевидным, что есть накладные расходы, которые вопиюще наблюдаются при генерации кода, и что накладные расходы даже, по-видимому, признаются людьми, ценными Microsoft! Тем не менее, я повторяю интернет...

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


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

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

Ясно, что мы должны оптимизировать наши приоритеты, а не только наш код! В моем последнем ответе я обратил внимание на различия между двумя фрагментами кода.

Используя 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

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

По мере того, как все больше полей вводятся в класс, весь этот кусочный мусор накапливается (как в исходном, так и в разобранном коде) намного выше разумных уровней. Четыре дополнительных строки на поле, и они всегда одни и те же линии... Не научились ли мы избегать повторения самих себя? Я полагаю, что мы могли бы скрыть try/catch за некоторой домашней абстракцией, но... тогда мы могли бы просто избегать исключений (т.е. Использовать Int.TryParse).

Это даже не сложный пример; Я видел попытки создания новых классов в try/catch. Учтите, что весь код внутри конструктора может быть затем дисквалифицирован из определенных оптимизаций, которые иначе были бы автоматически применены компилятором. Какой лучший способ объяснить теорию о том, что компилятор работает медленно, в отличие от компилятора делает именно то, что он сказал?

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

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

Я думаю, что это тоже положительный момент. Его можно использовать для отключения оптимизаций, которые в противном случае могли бы привести к ухудшению безопасности, разумным алгоритмам передачи сообщений для многопоточных приложений и уловить возможные условия гонки;) Это о единственном сценарии, который я могу придумать для использования try/catch. Даже у этого есть альтернативы.


Какие оптимизации делают try, catch и finally отключить?

A.K.A

Как try, catch и finally полезны в качестве вспомогательных средств для отладки?

они барьеры для записи. Это происходит из стандарта:

12.3.3.13 Утверждения о попытках

Для утверждения stmt формы:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
  • Определенное состояние присваивания v в начале try-блока такое же, как определенное состояние присваивания v в начале stmt.
  • Определенное состояние присваивания v в начале catch-блока-i (для любого i) совпадает с определенным состоянием присваивания v в начале stmt.
  • Определенное состояние присваивания v в конечной точке stmt определенно назначается, если (и только если) v определенно назначается в конечной точке try-block и каждый catch-block-i (для каждого я из 1 до n).

Другими словами, в начале каждого оператора try:

  • все назначения, сделанные видимым объектам перед вводом оператора try, должны быть завершены, что требует блокировки потока для запуска, что делает его полезным для отладки условий гонки!
  • компилятору запрещается:
    • устранить неиспользуемые назначения переменных, которые определенно были назначены перед оператором try
    • реорганизовать или объединить любые внутренние назначения (т.е. увидеть мою первую ссылку, если вы еще этого не сделали).
    • присвоения hoist по этому барьеру, чтобы отложить назначение переменной, которую он знает, не будет использоваться до тех пор, пока не будет (если вообще), или предварительно упреждающе переместить последующие назначения вперед, чтобы сделать другие оптимизации возможными...

Аналогичная история сохраняется для каждого оператора catch; предположим, что в вашем операторе try (или конструкторе или функции, который он вызывает и т.д.) вы назначаете эту переменную без смысла (скажем, garbage=42;), компилятор не может устранить эту инструкцию, независимо от того, насколько она неактуальна наблюдаемое поведение программы. Задание должно быть завершено до ввода блока catch.

Для чего он стоит, finally говорит об аналогичной деградирующей истории:

12.3.3.14. Утверждения Try-finally

Для утверждения try stt формы:

try try-block
finally finally-block

• Определенное состояние присваивания v в начале try-блока такое же, как определенное состояние присваивания v в начале stmt.
• Определенное состояние присваивания v в начале окончательного блока совпадает с определенным состоянием присваивания v в начале stmt.
• Определенное состояние присваивания v в конечной точке stmt определенно назначается, если (и только если):   o v определенно назначается в конечной точке try-block   o v определенно назначается в конечной точке finally-block Если выполняется передача потока управления (например, оператор goto), который начинается в try-блоке и заканчивается за пределами try-block, тогда v также считается определенно назначенным для этой передачи потока управления, если v определенно назначается в конце- точка окончательного блока. (Это не единственное, если: если v определенно назначается по другой причине при передаче этого потока управления, то он по-прежнему считается определенно назначенным.)

12.3.3.15. Утверждения о попытках-окончаний

Определенный анализ присваивания для утверждения try-catch-finally формы:

try try-block
catch ( ... ) catch-block-1
... 
catch ( ... ) catch-block-n
finally finally-block

выполняется так, как если бы оператор был инструкцией try-finally, содержащей инструкцию try-catch:

try { 
    try   
    try-block
    catch ( ... ) catch-block-1
    ...   
    catch ( ... ) catch-block-n
} 
finally finally-block

Ответ 12

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