Не пытайтесь ли вы блокировать блокировки, если исключения не выбрасываются?
Во время обзора кода с сотрудником Microsoft мы столкнулись с большой частью кода внутри блока try{}
. Она и ИТ-представитель предположили, что это может повлиять на производительность кода. Фактически, они предположили, что большая часть кода должна быть вне блоков try/catch и что необходимо проверять только важные разделы. Сотрудник Microsoft добавил и сказал, что предстоящий белый документ предупреждает о неправильных блоках try/catch.
Я огляделся и обнаружил, что может повлиять на оптимизацию, но, похоже, применяется только тогда, когда переменная распределяется между областями.
Я не спрашиваю о ремонтопригодности кода или даже обработке правильных исключений (этот код нуждается в повторном факторинге, без сомнения). Я также не имею в виду использование исключений для управления потоком, это явно неправильно в большинстве случаев. Это важные проблемы (некоторые из них важнее), но не здесь.
Как блоки try/catch влияют на производительность, если исключения не выбрасываются?
EDIT: Я добавляю щедрость. Есть интересные ответы, но я хотел бы получить еще несколько данных.
Ответы
Ответ 1
Проверьте это.
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
Ответ 2
После просмотра всех статистических данных с помощью try/catch и без try/catch любопытство заставило меня заглянуть назад, чтобы узнать, что создается для обоих случаев. Вот код:
С#:
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
С#:
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
, а функция возвращает.
Я могу с уверенностью предположить, что если исключений не происходит, то накладные расходы на создание локальных переменных содержат объекты исключений only и инструкцию перехода.
Ответ 3
Нет. Если тривиальная оптимизация, которую блок try/finally исключает, действительно оказывает измеримое влияние на вашу программу, вы, вероятно, не должны использовать .NET в первую очередь.
Ответ 4
Довольно подробное объяснение модели исключения .NET.
Rico Mariani Performance Tidbits: Исключительная стоимость: когда бросать и когда не в
Первый вид стоимости - статический стоимость обработки исключений в ваш код вообще. Управляемые исключения на самом деле здесь сравнительно неплохо, под которым я подразумеваю, что статическая стоимость может быть намного ниже, чем на С++. Почему это? Ну, статическая стоимость действительно понесенные в двух видах мест: Во-первых, фактические try/finally/catch/throw, где есть код для этих конструкций. Во-вторых, в неизменный код, там скрытность стоимости, связанной с отслеживанием все объекты, которые должны быть разрушается в случае, если исключение. Там есть значительное количество логики очистки которые должны присутствовать, и подлый часть состоит в том, что даже код, который не сам бросать или ловить или иначе иметь какое-либо откровенное использование исключений несет ответственность за знание того, как очистить после себя.
Дмитрий Заславский:
В соответствии с замечанием Криса Брумме: также стоимость, связанная с тем, что некоторая оптимизация не выполняемые JIT в присутствии улов
Ответ 5
Структура в примере отличается от Ben M. Он будет расширен накладными расходами внутри внутреннего цикла for
, что приведет к тому, что это не будет хорошим сравнением между этими двумя случаями.
Ниже приведено более точное сравнение, где весь код для проверки (включая объявление переменной) находится внутри блока Try/Catch:
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/Catch делает мало влияют на производительность.
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, на самом деле быстрее!
Ответ 6
Я тестировал фактическое воздействие try..catch
в узком цикле, и он слишком мал сам по себе, чтобы быть проблемой производительности в любой нормальной ситуации.
Если цикл работает очень мало (в моем тесте я сделал x++
), вы можете измерить влияние обработки исключений. Цикл с обработкой исключений занял примерно десять раз дольше.
Если цикл выполняет некоторую фактическую работу (в моем тесте я назвал метод Int32.Parse), обработка исключений слишком мало влияет на измеримость. Я получил гораздо большую разницу, заменив порядок петель...
Ответ 7
try catch блоки оказывают незначительное влияние на производительность, но исключение Throwing может быть довольно значительным, вероятно, там, где ваш коллега был сбит с толку.
Ответ 8
Попытка/уловка влияет на производительность.
Но это не огромное влияние. сложность try/catch обычно равна O (1), точно так же, как простое назначение, за исключением случаев, когда они помещаются в цикл. Поэтому вы должны использовать их с умом.
Здесь приведена ссылка о производительности try/catch (не объясняет сложность этого, хотя и подразумевается). Взгляните на раздел Отбросить меньшее количество исключений
Ответ 9
Теоретически блок 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
компилятор может оптимизировать оператор1 на основе того факта, что statement2 гарантированно выполняется перед statement3. Если компилятор может распознать, что вещь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()
before statement2 написал свое значение x
. Затем catch
выполнит Thread.ResetAbort()
[через делегат q
], что позволит продолжить выполнение с помощью оператора3. Разумеется, такая последовательность событий была бы невероятно невероятной, но компилятор должен генерировать код, который работает в соответствии со спецификацией, даже когда происходят такие невероятные события.
В общем, компилятор гораздо чаще замечает возможности оставить простые биты кода, чем сложные, и, следовательно, было бы редко, если try/catch может сильно повлиять на производительность, если исключения никогда не выбрасываются. Тем не менее, есть ситуации, когда существование блока try/catch может препятствовать оптимизации, которые - но для try/catch - позволили бы коду работать быстрее.
Ответ 10
См. обсуждение реализации try/catch для обсуждения того, как работают блоки try/catch, и как некоторые реализации имеют высокие накладные расходы, а некоторые имеют нулевые служебные данные,
когда не происходит никаких исключений. В частности, я думаю, что реализация 32-разрядной версии Windows имеет высокие накладные расходы, а реализация 64-битного кода не работает.