2009-08-20 2 views
194

Во время обзора кода с сотрудником Microsoft мы столкнулись с большой частью кода внутри блока try{}. Она и ИТ-представитель предположили, что это может повлиять на производительность кода. Фактически, они предположили, что большая часть кода должна быть вне блоков try/catch и что необходимо проверять только важные разделы. Сотрудник Microsoft добавил и сказал, что предстоящий белый документ предупреждает о неправильных блоках try/catch.Не пытайтесь ли блокировать блоки, если исключения не выбрасываются?

Я огляделся и нашел его can affect optimizations, но, похоже, применяется только в том случае, когда переменная разделяется между областями.

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

Как блоки try/catch влияют на производительность, если исключения составляют не thrown?

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

+125

«Тот, кто пожертвует правильностью для исполнения, не заслуживает». –

+1

Joel - Я ясно сказал, что это не фокус, я не занимаюсь микро-оптимизацией, и хороший код для меня более важен, я знаю лучше (я разработчик, не ИТ-парень). Это технический вопрос. – Kobi

+1

, чтобы вы ели исключения, а не пасли их? Обрабатываете ли вы случай отказа или что-то делаете? –

ответ

147

Проверьте это.

static public void Main(string[] args) 
{ 
    Stopwatch w = new Stopwatch(); 
    double d = 0; 

    w.Start(); 

    for (int i = 0; i < 10000000; i++) 
    { 
     try 
     { 
      d = Math.Sin(1); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.ToString()); 
     } 
    } 

    w.Stop(); 
    Console.WriteLine(w.Elapsed); 
    w.Reset(); 
    w.Start(); 

    for (int i = 0; i < 10000000; i++) 
    { 
     d = Math.Sin(1); 
    } 

    w.Stop(); 
    Console.WriteLine(w.Elapsed); 
} 

Выход:

00:00:00.4269033 // with try/catch 
00:00:00.4260383 // without. 

В миллисекундах:

449 
416 

Новый код:

for (int j = 0; j < 10; j++) 
{ 
    Stopwatch w = new Stopwatch(); 
    double d = 0; 
    w.Start(); 

    for (int i = 0; i < 10000000; i++) 
    { 
     try 
     { 
      d = Math.Sin(d); 
     } 

     catch (Exception ex) 
     { 
      Console.WriteLine(ex.ToString()); 
     } 

     finally 
     { 
      d = Math.Sin(d); 
     } 
    } 

    w.Stop(); 
    Console.Write(" try/catch/finally: "); 
    Console.WriteLine(w.ElapsedMilliseconds); 
    w.Reset(); 
    d = 0; 
    w.Start(); 

    for (int i = 0; i < 10000000; i++) 
    { 
     d = Math.Sin(d); 
     d = Math.Sin(d); 
    } 

    w.Stop(); 
    Console.Write("No try/catch/finally: "); 
    Console.WriteLine(w.ElapsedMilliseconds); 
    Console.WriteLine(); 
} 

Новые результаты:

try/catch/finally: 382 
No try/catch/finally: 332 

    try/catch/finally: 375 
No try/catch/finally: 332 

    try/catch/finally: 376 
No try/catch/finally: 333 

    try/catch/finally: 375 
No try/catch/finally: 330 

    try/catch/finally: 373 
No try/catch/finally: 329 

    try/catch/finally: 373 
No try/catch/finally: 330 

    try/catch/finally: 373 
No try/catch/finally: 352 

    try/catch/finally: 374 
No try/catch/finally: 331 

    try/catch/finally: 380 
No try/catch/finally: 329 

    try/catch/finally: 374 
No try/catch/finally: 334 
+18

Можете ли вы попробовать их в обратном порядке, чтобы быть уверенным, что компиляция JIT не повлияла на первое? – JoshJordan

+21

Такие программы вряд ли кажутся хорошими кандидатами для проверки влияния обработки исключений, слишком много того, что будет происходить в обычных блоках try {} catch {}, будет оптимизировано. Я могу обедать на этом ... – LorenVS

+0

+1 Для измерения. Интересно, будет ли try/finally иметь такую ​​же относительную статистику, как try/catch? – Joseph

27

Quite comprehensive explanation of the .NET exception model.

Rico Mariani в Tidbits Производительность: Exception Cost: When to throw and when not to

Первый вид стоимости является статичным стоимость наличия обработки исключений в коде вообще. Управляемые исключения на самом деле делаю сравнительно неплохо, , под которым я подразумеваю статическую стоимость, может быть намного ниже, чем на C++. Почему это? Ну, статическая стоимость действительно , понесенная в двух видах мест: Во-первых, фактические сайты try/finally/catch/throw, где есть код для этих конструкций. Во-вторых, в невыполненный код, есть стелс стоимость, связанная с отслеживанием , все объекты, которые должны быть , уничтожены в случае, если выбрано исключение . Там в значительное количество очищающей логики , который должен присутствовать и подлый часть является то, что даже код, который не сам бросить или поймать или иначе имеют никакого явного использования исключений еще несет бремя зная, как очистить после себя.

Дмитрий Заславский:

В соответствии с запиской Криса Brumme в: Существует также расходы, связанные с фактически некоторые оптимизации не будучи в исполнении JIT в присутствии улова

+1

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

+0

Thnx за ссылку! Хороший. – Henri

+0

Заявки Рико Мариани совершенно неправы для родного С ++. «статическая стоимость может быть намного ниже, чем в C++». Это просто неверно.Хотя, я не уверен, какой был дизайн механизма исключения в 2003 году, когда была написана статья. C++ действительно ** вообще не имеет стоимости **, если исключения ** не выбрасываются **, независимо от того, сколько у вас блоков try/catch и где они. – BJovke

45

Нет. Если тривиальная оптимизация блока try/finally исключает фактическое влияние на вашу программу, вы, вероятно, не должны использовать .NET в первых т.

+8

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

12

Я тестировал фактическое воздействие try..catch в плотной петле, и он слишком мал сам по себе, чтобы быть проблемой с производительностью в любой нормальной ситуации.

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

Если цикл выполняет некоторую фактическую работу (в моем тесте я вызвал метод Int32.Parse), обработка исключений слишком мало влияет на измеримость. Я получил гораздо большую разницу, заменив порядок петель ...

8

try catch блоки оказывают незначительное влияние на производительность, но исключение. Бросок может быть довольно значительным, вероятно, там, где ваш коллега был сбит с толку.

18

В другом варианте структура от Ben M. Он будет расширен накладные расходы внутри внутреннего цикла for, что приведет к тому, что это не будет хорошим сравнением между этими двумя случаями.

Следующая является более точным, для сравнения, где весь код проверки (в том числе объявления переменной) находится внутри Try/Выгода блока:

 for (int j = 0; j < 10; j++) 
     { 
      Stopwatch w = new Stopwatch(); 
      w.Start(); 
      try { 
       double d1 = 0; 
       for (int i = 0; i < 10000000; i++) { 
        d1 = Math.Sin(d1); 
        d1 = Math.Sin(d1); 
       } 
      } 
      catch (Exception ex) { 
       Console.WriteLine(ex.ToString()); 
      } 
      finally { 
       //d1 = Math.Sin(d1); 
      } 
      w.Stop(); 
      Console.Write(" try/catch/finally: "); 
      Console.WriteLine(w.ElapsedMilliseconds); 
      w.Reset(); 
      w.Start(); 
      double d2 = 0; 
      for (int i = 0; i < 10000000; i++) { 
       d2 = Math.Sin(d2); 
       d2 = Math.Sin(d2); 
      } 
      w.Stop(); 
      Console.Write("No try/catch/finally: "); 
      Console.WriteLine(w.ElapsedMilliseconds); 
      Console.WriteLine(); 
     } 

Когда я запустил оригинальный тестовый код из Ben M, Я заметил разницу в настройках Debug и Release.

Эта версия, я заметил разницу в отладочной версии (фактически больше, чем в другой версии), но это не было никакой разницей в версии Release.

Conclution:
На основании этих испытаний, я думаю, мы можем сказать, что Try/Поймать делает оказывает незначительное влияние на производительность.

EDIT:
Я попытался увеличить значение петли от 10000000 до 1000000000, и снова побежал в Release, чтобы получить некоторые различия в выпуске, и результат был такой:

try/catch/finally: 509 
No try/catch/finally: 486 

    try/catch/finally: 479 
No try/catch/finally: 511 

    try/catch/finally: 475 
No try/catch/finally: 477 

    try/catch/finally: 477 
No try/catch/finally: 475 

    try/catch/finally: 475 
No try/catch/finally: 476 

    try/catch/finally: 477 
No try/catch/finally: 474 

    try/catch/finally: 475 
No try/catch/finally: 475 

    try/catch/finally: 476 
No try/catch/finally: 476 

    try/catch/finally: 475 
No try/catch/finally: 476 

    try/catch/finally: 475 
No try/catch/finally: 474 

Вы видите что результат является непоследовательным. В некоторых случаях версия с использованием Try/Catch на самом деле быстрее!

+1

Я тоже это заметил, иногда быстрее с try/catch. Я прокомментировал это на ответ Бена. Однако, в отличие от 24 избирателей, мне не нравится такой бенчмаркинг, я не думаю, что это хороший показатель. В этом случае код быстрее, но всегда ли это будет? – Kobi

+3

Разве это не доказывает, что ваша машина выполняла множество других задач одновременно? Истекшее время никогда не является хорошей мерой, вам нужно использовать профилировщик, который регистрирует время процессора, а не истекшее время. –

+1

@Kobi: Я утверждаю, что это не лучший способ сравнения, если вы собираетесь опубликовать его в качестве доказательства того, что ваша программа работает быстрее, чем другие или что-то в этом роде, но может дать вам как разработчику указание на один метод, выполняющий лучше, чем другой. В этом случае, я думаю, мы можем сказать, что различия (по крайней мере, для конфигурации Release) невежественны. – awe

5

Попытка/уловка влияет на производительность.

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

Here - ссылка на производительность try/catch (не объясняет сложность этого, хотя и подразумевается). Посмотрите на Бросьте Меньшее количество исключений раздела

+0

Буду признателен за материал для чтения. – Kobi

+0

Я добавляю ссылку. – Isaac

+0

Сложность O (1) это не означает слишком много. Например, если вы оборудуете раздел кода, который очень часто называется try-catch (или вы упоминаете цикл), O (1) s может добавить до измеримого числа в конце. –

67

После просмотра все статистики по с TRY/уловом и без TRY/улова, любопытство заставило меня смотреть за, чтобы увидеть, что генерируется для обоего случаев. Вот код:

C#:

private static void TestWithoutTryCatch(){ 
    Console.WriteLine("SIN(1) = {0} - No Try/Catch", Math.Sin(1)); 
} 

MSIL:

.method private hidebysig static void TestWithoutTryCatch() cil managed 
{ 
    // Code size  32 (0x20) 
    .maxstack 8 
    IL_0000: nop 
    IL_0001: ldstr  "SIN(1) = {0} - No Try/Catch" 
    IL_0006: ldc.r8  1. 
    IL_000f: call  float64 [mscorlib]System.Math::Sin(float64) 
    IL_0014: box  [mscorlib]System.Double 
    IL_0019: call  void [mscorlib]System.Console::WriteLine(string, 
                   object) 
    IL_001e: nop 
    IL_001f: ret 
} // end of method Program::TestWithoutTryCatch 

C#:

private static void TestWithTryCatch(){ 
    try{ 
     Console.WriteLine("SIN(1) = {0}", Math.Sin(1)); 
    } 
    catch (Exception ex){ 
     Console.WriteLine(ex); 
    } 
} 

MSIL:

.method private hidebysig static void TestWithTryCatch() cil managed 
{ 
    // Code size  49 (0x31) 
    .maxstack 2 
    .locals init ([0] class [mscorlib]System.Exception ex) 
    IL_0000: nop 
    .try 
    { 
    IL_0001: nop 
    IL_0002: ldstr  "SIN(1) = {0}" 
    IL_0007: ldc.r8  1. 
    IL_0010: call  float64 [mscorlib]System.Math::Sin(float64) 
    IL_0015: box  [mscorlib]System.Double 
    IL_001a: call  void [mscorlib]System.Console::WriteLine(string, 
                    object) 
    IL_001f: nop 
    IL_0020: nop 
    IL_0021: leave.s IL_002f //JUMP IF NO EXCEPTION 
    } // end .try 
    catch [mscorlib]System.Exception 
    { 
    IL_0023: stloc.0 
    IL_0024: nop 
    IL_0025: ldloc.0 
    IL_0026: call  void [mscorlib]System.Console::WriteLine(object) 
    IL_002b: nop 
    IL_002c: nop 
    IL_002d: leave.s IL_002f 
    } // end handler 
    IL_002f: nop 
    IL_0030: ret 
} // end of method Program::TestWithTryCatch 

Я не эксперт в области ИЛ, но мы видим, что локальный объект исключения создается на четвертой строке .locals init ([0] class [mscorlib]System.Exception ex), после чего все происходит так же, как и для метода без try/catch до строки семнадцати IL_0021: leave.s IL_002f. Если возникает исключение, управление переходит к строке IL_0025: ldloc.0, иначе мы переходим к метке IL_002d: leave.s IL_002f и возвращаем функцию.

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

+2

+1 это лучший ответ. Различия между ними очень четкие (и мягкие). – HuBeZa

+22

Ну, ИЛ включает блок try/catch в том же обозначении, что и в C#, поэтому на самом деле это не показывает, сколько накладных затрат на попытку/улов за кулисами! Просто, что IL не добавляет намного больше, это не значит, что он не добавляет что-то в скомпилированный код сборки. IL - это просто общее представление всех языков .NET. Это НЕ машинный код! – awe

3

См. discussion on try/catch implementation для обсуждения того, как работают блоки try/catch, и как некоторые реализации имеют высокие накладные расходы, а некоторые имеют нулевые служебные данные, , когда не происходит никаких исключений. В частности, я думаю, что реализация 32-разрядной версии Windows имеет высокие накладные расходы, а 64-разрядная реализация - нет.

+0

Я описал два разных подхода к реализации исключений. Подходы применяются в равной степени к C++ и C#, а также к управляемому/неуправляемому коду. Какие MS выбрали для своего C#, я точно не знаю, но архитектура обработки исключений приложений на уровне машины, предоставляемых MS, использует более быструю схему. Я был бы немного удивлен, если бы реализация C# для 64 бит не использовала его. –

+0

Хм, я прочитаю об этом больше. –

4

Теоретически блок try/catch не будет влиять на поведение кода, если на самом деле не возникает исключение. Однако есть некоторые редкие обстоятельства, когда наличие блока try/catch может иметь большой эффект, а также некоторые необычные, но едва заметные, где эффект может быть заметным. Причина этого заключается в том, что данный код как:

Action q; 
double thing1() 
    { double total; for (int i=0; i<1000000; i++) total+=1.0/i; return total;} 
double thing2() 
    { q=null; return 1.0;} 
... 
x=thing1();  // statement1 
x=thing2(x); // statement2 
doSomething(x); // statement3 

компилятор может быть в состоянии оптимизировать statement1 на основе того факта, что оператор2 гарантированно выполняться до оператор3. Если компилятор может распознать, что вещь1 не имеет побочных эффектов, а вещь2 фактически не использует x, она может полностью опустить вещь1. Если [как в этом случае] вещь1 была дорогой, это могла бы быть крупная оптимизация, хотя случаи, когда вещь1 стоит дорого, также таковы, что компилятор вряд ли будет оптимизировать. Предположим, что код был изменен:

x=thing1();  // statement1 
try 
{ x=thing2(x); } // statement2 
catch { q(); } 
doSomething(x); // statement3 

Теперь существует последовательность событий, в которых statement3 может выполняться без выполнения инструкции2.Даже если ничто в коде для thing2 не может вызвать исключение, было бы возможно, что в другом потоке можно было бы использовать Interlocked.CompareExchange, чтобы заметить, что q был очищен и установлен в Thread.ResetAbort, а затем выполнить Thread.Abort(), прежде чем statement2 напишет свое значение на x. Затем catch выполнит Thread.ResetAbort() [через делегат q], что позволит продолжить выполнение с помощью инструкции 3. Конечно, такая последовательность событий была бы невероятно невероятной, но компилятор должен генерировать код, который работает в соответствии со спецификацией, даже когда происходят такие невероятные события.

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