Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Первые были освещены в нескольких сообщениях в блогах 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.Учтите, что весь код внутри конструктора может быть исключен из определенных оптимизаций, которые в противном случае автоматически применялись бы компилятором.Какой лучший способ дать начало теории о том, что компилятор медленный, в отличие от компилятор делает именно то, что ему говорят?

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

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

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


Что дает оптимизация try, catch и finally запрещать?

А.К.А.

Как try, catch и finally полезен в качестве средства отладки?

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

12.3.3.13 Операторы Try-Catch

Для заявления стмт формы:

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

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

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

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

Чего бы это ни стоило, finally рассказывает аналогично унижающий достоинство история:

12.3.3.14 Операторы Try-finally

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

try try-block
finally finally-block

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

12.3.3.15 Операторы Try-Catch-finally

Анализ определенного назначения для пытаться-ловить-окончательно заявление по форме:

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

делается так, как если бы утверждение было пытаться-окончательно заявление, включающее пытаться-ловить заявление:

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top