在一次微软员工的代码检查中,我们在一个try{}块中发现了一大块代码。她和一位IT代表表示,这可能会对代码的性能产生影响。事实上,他们建议大部分代码应该在try/catch块之外,并且只检查重要的部分。这位微软员工补充说,即将发布的白皮书对错误的尝试/捕获块提出了警告。
我环顾四周,发现它会影响优化,但它似乎只适用于在作用域之间共享变量时。
我不是在问代码的可维护性,甚至不是在问如何处理正确的异常(毫无疑问,有问题的代码需要重构)。我也不是指使用异常进行流控制,这在大多数情况下显然是错误的。这些都是重要的问题(有些更重要),但不是这里的重点。
当不抛出异常时,try/catch块如何影响性能?
理论上,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。如果thing1是昂贵的,这可能是一个主要的优化,尽管thing1是昂贵的情况也是编译器最不可能优化的。假设代码被更改:
x=thing1(); // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x); // statement3
Now there exists a sequence of events where statement3 could execute without statement2 having executed. Even if nothing in the code for thing2 could throw an exception, it would be possible that another thread could use an Interlocked.CompareExchange to notice that q was cleared and set it to Thread.ResetAbort, and then perform a Thread.Abort() before statement2 wrote its value to x. Then the catch would execute Thread.ResetAbort() [via delegate q], allowing execution to continue with statement3. Such a sequence of events would of course be exceptionally improbable, but a compiler is required to generate code which work according to specification even when such improbable events occur.
一般来说,编译器更有可能注意到遗漏简单代码而不是复杂代码的机会,因此如果从未抛出异常,try/catch很少会对性能产生很大影响。尽管如此,在某些情况下,try/catch块的存在可能会阻止优化——如果没有try/catch的话——可以让代码运行得更快。
. net异常模型的非常全面的解释。
Rico Mariani的性能花絮:异常成本:何时抛出,何时不抛出
The first kind of cost is the static
cost of having exception handling in
your code at all. Managed exceptions
actually do comparatively well here,
by which I mean the static cost can be
much lower than say in C++. Why is
this? Well, static cost is really
incurred in two kinds of places:
First, the actual sites of
try/finally/catch/throw where there's
code for those constructs. Second, in
unmanged code, there's the stealth
cost associated with keeping track of
all the objects that must be
destructed in the event that an
exception is thrown. There's a
considerable amount of cleanup logic
that must be present and the sneaky
part is that even code that doesn't
itself throw or catch or otherwise
have any overt use of exceptions still
bears the burden of knowing how to
clean up after itself.
德米特里·扎斯拉夫斯基:
根据Chris Brumme的注释:有
还有一个与事实有关的成本
有些优化没有进行
由JIT在现场执行
抓
理论上,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。如果thing1是昂贵的,这可能是一个主要的优化,尽管thing1是昂贵的情况也是编译器最不可能优化的。假设代码被更改:
x=thing1(); // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x); // statement3
Now there exists a sequence of events where statement3 could execute without statement2 having executed. Even if nothing in the code for thing2 could throw an exception, it would be possible that another thread could use an Interlocked.CompareExchange to notice that q was cleared and set it to Thread.ResetAbort, and then perform a Thread.Abort() before statement2 wrote its value to x. Then the catch would execute Thread.ResetAbort() [via delegate q], allowing execution to continue with statement3. Such a sequence of events would of course be exceptionally improbable, but a compiler is required to generate code which work according to specification even when such improbable events occur.
一般来说,编译器更有可能注意到遗漏简单代码而不是复杂代码的机会,因此如果从未抛出异常,try/catch很少会对性能产生很大影响。尽管如此,在某些情况下,try/catch块的存在可能会阻止优化——如果没有try/catch的话——可以让代码运行得更快。