2008-10-02 5 views
131

Я не хочу обсуждать, когда и не бросать исключения. Я хочу решить простую проблему. В 99% случаев аргумент о том, что не исключение исключений, вращается вокруг них медленным, в то время как другая сторона утверждает (с контрольным тестом), что скорость не является проблемой. Я читал многочисленные блоги, статьи и сообщения, относящиеся к той или другой стороне. Так что это?Насколько медленны исключения .NET?

Некоторые ссылки из ответов: Skeet, Mariani, Brumme.

+12

есть ложь, проклятая ложь и бенчмарки. :) – gbjbaanb 2008-10-02 12:42:14

+0

К сожалению, несколько высоких голосовых ответов здесь пропустили, что вопрос задает вопрос «насколько медленными являются исключения?» И конкретно просили избежать темы о том, как часто их использовать. Один простой ответ на вопрос, который, как на самом деле, спросил ..... В Windows CLR исключения в 750 раз медленнее, чем возвращаемые значения. – 2016-07-03 04:58:15

ответ

189

Я нахожусь на «не медленной» стороне - или, точнее, «не настолько медленной, чтобы ее можно было избежать при обычном использовании». Я написал два shortarticles. Существуют критические замечания по эталонному аспекту, которые в основном сводятся к тому, что «в реальной жизни будет больше стека, чтобы вы могли взорвать кеш и т. Д.», Но с использованием кодов ошибок для работы в стеке было бы также взорвать кеш, поэтому я не вижу в этом особого аргумента.

Просто, чтобы все было ясно - я не поддерживаю использование исключений, если они нелогичны. Например, int.TryParse полностью подходит для преобразования данных от пользователя. Это удобно при чтении машинного файла, где сбой означает: «Файл не в том формате, в котором он должен быть, я действительно не хочу пытаться справиться с этим, поскольку я не знаю, что еще может быть неправильно. "

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

+3

Отличная статья. – MusiGenesis 2008-10-02 12:33:22

+2

К сожалению, людям говорят, что исключения бесплатны, используйте их для тривиальной «правильной» функциональности, они должны использоваться, как вы говорите, когда что-то пошло не так - в «исключительных» обстоятельствах – gbjbaanb 2008-10-02 12:36:31

+4

Да, люди должны обязательно знать, что есть производительность стоимость, связанная с неправильным использованием исключений. Я просто думаю, что это не проблема, когда они * используются соответствующим образом :) – 2008-10-02 12:40:27

1

В режиме выпуска накладные расходы минимальны.

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

5

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

Часто в обычной логике приложения вы выполняете цикл, где одно и то же действие повторяется тысячи/миллионы раз. В этом случае с очень простым профилированием (см. Класс Stopwatch) вы можете сами убедиться, что исключение вместо простого утверждения if может оказаться значительно медленнее.

На самом деле я однажды прочитал, что команда .NET в Microsoft представила методы TryXXXXX в .NET 2.0 для многих базовых типов FCL специально потому, что клиенты жаловались на то, что производительность их приложений была настолько медленной.

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

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

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

3

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

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

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

В конце дня они являются действительной языковой функцией.

Просто чтобы доказать мою точку

Пожалуйста, запустите code at this link (слишком большой для ответа).

Результаты на моем компьютере:

[email protected]:~/develop/test$ mono Exceptions.exe | grep PM
10/2/2008 2:53:32 PM
10/2/2008 2:53:42 PM
10/2/2008 2:53:52 PM

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

2

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

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

27

Ответ на этот вопрос от парня, который их реализовал, - это Крис Брумме. Он написал excellent blog article о предмете (предупреждение - его очень долго) (warning2 - его очень хорошо написано, если вы техником, вы прочтете его до конца, а затем должны будете отработать свои часы после работы :))

Резюме: они медленные. Они реализованы в виде исключений Win32 SEH, поэтому некоторые из них даже передадут границу 0 на границе 0! Очевидно, что в реальном мире вы будете выполнять много другой работы, поэтому нечетное исключение не будет замечено вообще, но если вы используете их для потока программы, за исключением того, что ваше приложение будет забито. Это еще один пример маркетинговой машины MS, делающей нам плохую услугу. Я помню одну микрософт, рассказывающую нам, как они понесли абсолютно нулевые накладные расходы, что является полным тошем.

Крис дает уместную цитату:

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

7

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

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

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

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

Кроме того, в соответствии с моей копией «Тестирование производительности Microsoft .NET веб-приложений», написанного ACE командой:

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

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

4

Мой XMPP-сервер получил значительное повышение скорости (извините, никаких фактических чисел, чисто наблюдательных) после того, как я последовательно пытался предотвратить их (например, проверить, подключен ли сокет, прежде чем пытаться читать больше данных) и отдавать себя способы избежать их (упомянутые методы TryX). Это было только около 50 активных виртуальных пользователей (в чате).

0

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

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

2

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

2

Но моно выбрасывает исключение в 10 раз быстрее, чем.net standalone mode, и .net автономный режим генерирует исключение 60x быстрее, чем режим отладки .net. (машины тестирования имеют ту же модель CPU)

int c = 1000000; 
int s = Environment.TickCount; 
for (int i = 0; i < c; i++) 
{ 
    try { throw new Exception(); } 
    catch { } 
} 
int d = Environment.TickCount - s; 

Console.WriteLine(d + "ms/" + c + " exceptions"); 
1

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

Эта высокая стоимость исключений объясняется тем, что Windows CLR интегрируется с чем-то, что называется Windows Structured Exception Handling. Это позволяет исключить правильные уловки и перехватывать разные режимы работы и языки. Однако это очень медленно.

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

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

baseline: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 13.0007 ms 
baseline: recurse_depth 8, error_freqeuncy 0.25 (0), time elapsed 13.0007 ms 
baseline: recurse_depth 8, error_freqeuncy 0.5 (0), time elapsed 13.0008 ms 
baseline: recurse_depth 8, error_freqeuncy 0.75 (0), time elapsed 13.0008 ms 
baseline: recurse_depth 8, error_freqeuncy 1 (0), time elapsed 14.0008 ms 
retval_error: recurse_depth 5, error_freqeuncy 0 (0), time elapsed 13.0008 ms 
retval_error: recurse_depth 5, error_freqeuncy 0.25 (249999), time elapsed 14.0008 ms 
retval_error: recurse_depth 5, error_freqeuncy 0.5 (499999), time elapsed 16.0009 ms 
retval_error: recurse_depth 5, error_freqeuncy 0.75 (999999), time elapsed 16.001 ms 
retval_error: recurse_depth 5, error_freqeuncy 1 (999999), time elapsed 16.0009 ms 
retval_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 20.0011 ms 
retval_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 21.0012 ms 
retval_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 24.0014 ms 
retval_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 24.0014 ms 
retval_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 24.0013 ms 
exception_error: recurse_depth 8, error_freqeuncy 0 (0), time elapsed 31.0017 ms 
exception_error: recurse_depth 8, error_freqeuncy 0.25 (249999), time elapsed 5607.3208  ms 
exception_error: recurse_depth 8, error_freqeuncy 0.5 (499999), time elapsed 11172.639 ms 
exception_error: recurse_depth 8, error_freqeuncy 0.75 (999999), time elapsed 22297.2753 ms 
exception_error: recurse_depth 8, error_freqeuncy 1 (999999), time elapsed 22102.2641 ms 

А вот код ..

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace ConsoleApplication1 { 

public class TestIt { 
    int value; 

    public class TestException : Exception { } 

    public int getValue() { 
     return value; 
    } 

    public void reset() { 
     value = 0; 
    } 

    public bool baseline_null(bool shouldfail, int recurse_depth) { 
     if (recurse_depth <= 0) { 
      return shouldfail; 
     } else { 
      return baseline_null(shouldfail,recurse_depth-1); 
     } 
    } 

    public bool retval_error(bool shouldfail, int recurse_depth) { 
     if (recurse_depth <= 0) { 
      if (shouldfail) { 
       return false; 
      } else { 
       return true; 
      } 
     } else { 
      bool nested_error = retval_error(shouldfail,recurse_depth-1); 
      if (nested_error) { 
       return true; 
      } else { 
       return false; 
      } 
     } 
    } 

    public void exception_error(bool shouldfail, int recurse_depth) { 
     if (recurse_depth <= 0) { 
      if (shouldfail) { 
       throw new TestException(); 
      } 
     } else { 
      exception_error(shouldfail,recurse_depth-1); 
     } 

    } 

    public static void Main(String[] args) { 
     int i; 
     long l; 
     TestIt t = new TestIt(); 
     int failures; 

     int ITERATION_COUNT = 1000000; 


     // (0) baseline null workload 
     for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { 
      for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {    
       int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f/exception_freq);    

       failures = 0; 
       DateTime start_time = DateTime.Now; 
       t.reset();    
       for (i = 1; i < ITERATION_COUNT; i++) { 
        bool shoulderror = (i % EXCEPTION_MOD) == 0; 
        t.baseline_null(shoulderror,recurse_depth); 
       } 
       double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; 
       Console.WriteLine(
        String.Format(
         "baseline: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", 
         recurse_depth, exception_freq, failures,elapsed_time)); 
      } 
     } 


     // (1) retval_error 
     for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { 
      for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {    
       int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f/exception_freq);    

       failures = 0; 
       DateTime start_time = DateTime.Now; 
       t.reset();    
       for (i = 1; i < ITERATION_COUNT; i++) { 
        bool shoulderror = (i % EXCEPTION_MOD) == 0; 
        if (!t.retval_error(shoulderror,recurse_depth)) { 
         failures++; 
        } 
       } 
       double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; 
       Console.WriteLine(
        String.Format(
         "retval_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", 
         recurse_depth, exception_freq, failures,elapsed_time)); 
      } 
     } 

     // (2) exception_error 
     for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { 
      for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {    
       int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f/exception_freq);    

       failures = 0; 
       DateTime start_time = DateTime.Now; 
       t.reset();    
       for (i = 1; i < ITERATION_COUNT; i++) { 
        bool shoulderror = (i % EXCEPTION_MOD) == 0; 
        try { 
         t.exception_error(shoulderror,recurse_depth); 
        } catch (TestException e) { 
         failures++; 
        } 
       } 
       double elapsed_time = (DateTime.Now - start_time).TotalMilliseconds; 
       Console.WriteLine(
        String.Format(
         "exception_error: recurse_depth {0}, error_freqeuncy {1} ({2}), time elapsed {3} ms", 
         recurse_depth, exception_freq, failures,elapsed_time));   } 
     } 
    } 
} 


} 
-1

При написании классов/функций для других, чтобы использовать это, кажется, трудно сказать, когда исключения уместны. Есть некоторые полезные части BCL, которые я должен был канавать и идти на pinvoke, потому что они бросают исключения вместо того, чтобы возвращать ошибки. В некоторых случаях вы можете обойти это, но для других, таких как System.Management и Performance Counters, есть способы использования, где вам нужно делать циклы, в которых исключения часто генерируются BCL.

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

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

Смежные вопросы