当不抛出exception时,是否尝试/捕获块会损害性能?
在与微软员工进行代码审查的过程中,我们遇到了try{}
块中的大部分代码。 她和IT代表build议,这可能会影响代码的执行。 实际上,他们build议大部分的代码应该在try / catch块之外,只有重要的部分应该被检查。 微软员工补充说,即将发布的白皮书警告不正确的try / catch块。
我环顾四周,发现它可以影响优化 ,但似乎只适用于范围之间共享variables。
我并不是在询问代码的可维护性,甚至是处理正确的exception(这个代码无疑需要重新分解)。 我也不是指使用exception进行stream量控制,这在大多数情况下显然是错误的。 这些是重要的问题(有些更重要),但不是重点。
try / catch块在不抛出exception时如何影响性能?
编辑:我添加一个赏金。 有一些有趣的回应,但我想获得更多的意见。
核实。
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
看到所有的try / catch和没有try / catch的统计数据后,好奇心迫使我往后看,看看这两种情况产生了什么。 这里是代码:
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
我不是IL的专家,但是我们可以看到,在第四行.locals init ([0] class [mscorlib]System.Exception ex)
创build了一个局部exception对象,事情和没有try /赶上17 IL_0021: leave.s IL_002f
线IL_0021: leave.s IL_002f
。 如果发生exception,则控制跳转到IL_0025: ldloc.0
行IL_0025: ldloc.0
否则我们跳转到标签IL_002d: leave.s IL_002f
并返回函数。
我可以安全地假设,如果没有发生exception,那么创build局部variables仅用于保存exception对象和跳转指令是开销。
不可以。如果try / finally块排除的微小优化实际上会对程序产生可测量的影响,那么您可能不应该首先使用.NET。
.NETexception模型的完整的解释。
Rico Mariani的performance花絮: 例外花费:什么时候投掷,何时不投掷
第一种成本是在代码中进行exception处理的静态成本。 在这里,被pipe理的exception事实上做得相当好,我的意思是说静态成本可以比C ++中低很多。 为什么是这样? 那么,静态成本实际上是在两种地方产生的:一是try / finally / catch / throw这些构造的代码的实际位置。 其次,在unmanged代码中,隐藏成本与追踪所有必须在抛出exception的情况下被破坏的对象有关。 有相当数量的清理逻辑必须存在,而且鬼鬼祟祟的部分是,即使是本身没有抛出或捕获或以其他方式公开使用exception的代码,仍然承担着知道如何自行清理的负担。
Dmitriy Zaslavskiy:
根据克里斯·布鲁姆(Chris Brumme)的说法:还有一个成本与一些优化没有被JIT执行的情况有关
本M的例子中的结构是不同的。 它会在内部for
循环内部被扩展开for
,这将导致它不能很好的比较两种情况。
以下是比较准确的比较,其中要检查的整个代码(包括variables声明)在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的原始testing代码时,我注意到了Debug和Releasconfiguration的差异。
这个版本,我注意到debugging版本(实际上比其他版本更多)的差异,但在发行版本没有区别。
结论 :
基于这些testing,我认为我们可以说Try / Catch对性能影响不大。
编辑:
我试图将循环值从1000万增加到10亿,并且在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的版本实际上更快!
我在一个紧密的循环中testing了一个try..catch
的实际影响,在任何正常的情况下,它本身就是一个性能问题。
如果循环做了很less的工作(在我的testing中,我做了一个x++
),你可以测量exception处理的影响。 具有exception处理的循环花费了大约十倍的时间来运行。
如果循环做了一些实际的工作(在我的testing中,我调用了Int32.Parse方法),exception处理的影响太小,无法衡量。 通过交换循环顺序,我得到了更大的差异…
尝试catch块对性能的影响可以忽略不计,但exception投掷可以相当大,这可能是你的同事困惑的地方。
try / catch对性能有影响。
但是这不是一个巨大的影响。 try / catch的复杂性通常是O(1),就像一个简单的赋值,除非它们被放置在一个循环中。 所以你必须明智地使用它们。
这里有一个关于try / catch性能的参考(虽然没有解释它的复杂性,但它是隐含的)。 看看抛出更less的exception部分
理论上,除非实际发生exception,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
编译器可以基于statement2保证在statement3之前执行的事实来优化statement1。 如果编译器可以识别出thing1没有副作用,而thing2实际上并不使用x,那么可以完全省略thing1。 如果[在这种情况下]东西很贵,这可能是一个主要的优化,尽pipe东西1昂贵的情况也是那些编译器最不可能优化的东西。 假设代码被改变了:
x=thing1(); // statement1 try { x=thing2(x); } // statement2 catch { q(); } doSomething(x); // statement3
现在存在一系列的事件,statement3可以执行而statement2没有被执行。 即使thing2
的代码中没有任何东西可以抛出exception,也可能是另一个线程可能使用Interlocked.CompareExchange
来注意到q
被清除并将其设置为Thread.ResetAbort
,然后执行Thread.Abort()
statement2将其值写入x
。 然后catch
会执行Thread.ResetAbort()
[通过委托q
],允许执行继续statement3。 这样的一系列事件当然是非常不可能的,但是需要编译器来生成符合规范的代码,即使发生这种不可能的事件。
一般来说,编译器更容易注意到比简单代码less的代码,因此try / catch很less会影响性能,如果从不抛出exception。 尽pipe如此,在某些情况下,try / catch块的存在可能会阻止优化,但是对于try / catch而言,这会使代码运行得更快。
关于try / catch实现的讨论,讨论try / catch块如何工作,以及一些实现如何具有高开销,有些没有任何exception发生时的零开销。 特别是,我认为Windows 32位实现有很高的开销,而64位实现没有。
- 为什么SSE标量sqrt(x)慢于rsqrt(x)* x?
- 在PHP $ array = $ value或array_push($ array,$ value)中使用什么更好?
- 为什么variables1 + =variables2比variables1 =variables1 +variables2快得多?
- 这是一个“足够好”的随机algorithm; 为什么不使用,如果它更快?
- 在ruby中使用单引号与双引号是否有性能提升?
- 在SQL Server中分页结果的最佳方法是什么?
- 增强webView性能(应该与原生Web浏览器性能相同)
- 什么是提高NHibernate性能的最佳方法?
- 为什么提升matrix乘法比我的慢?