在与Microsoft员工进行代码审查期间,我们在 try{}
块中遇到了大量代码 . 她和IT代表建议这可能会影响代码的性能 . 事实上,他们建议大多数代码应该在try / catch块之外,并且只应该检查重要的部分 . 微软员工补充说,即将发布的白皮书警告不要使用不正确的try / catch块 .
我环顾四周,发现它can affect optimizations,但它似乎只适用于范围之间共享变量 .
我不是在询问代码的可维护性,甚至不是在处理正确的异常(有问题的代码需要重新分解,毫无疑问) . 我也没有提到使用流量控制的异常,这在大多数情况下显然是错误的 . 这些都是重要的问题(有些更重要),但不是重点 .
如果不抛出异常,try / catch块如何影响性能?
10 回答
try / catch HAS对性能的影响 .
但它不是一个巨大的影响 . try / catch复杂度通常是O(1),就像一个简单的赋值,除非它们放在一个循环中 . 所以你必须明智地使用它们 .
Here是关于try / catch性能的参考(虽然没有解释它的复杂性,但它暗示了) . 看一下 Throw Fewer Exceptions 部分
核实 .
输出:
以毫秒为单位:
新代码:
新结果:
在看到try / catch和没有try / catch的所有统计数据后,好奇心迫使我向后看,看看两个案例的生成情况 . 这是代码:
C#:
MSIL:
C#:
MSIL:
我不是IL的专家,但是我们可以看到在第四行
.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
并返回函数 .我可以安全地假设,如果没有异常发生,那么创建局部变量以仅保存异常对象和跳转指令的开销 .
不可以 . 如果try / finally块排除的微不足道优化实际上对您的程序产生了可测量的影响,那么您可能不应该首先使用.NET .
Quite comprehensive explanation of the .NET exception model.
Rico Mariani的表演花絮:Exception Cost: When to throw and when not to
Dmitriy Zaslavskiy:
该示例中的结构与 Ben M 不同 . 它将在内部
for
循环内部扩展,这将导致它在两种情况之间不能很好地进行比较 .以下更准确的比较,其中要检查的整个代码(包括变量声明)在Try / Catch块内:
当我从 Ben M 运行原始测试代码时,我注意到Debug和Releas配置都有区别 .
这个版本,我注意到调试版本的差异(实际上比其他版本更多),但它在Release版本中没有区别 .
Conclution :
根据这些测试,我认为我们可以说Try / Catch does 对性能的影响很小 .
EDIT:
我试图将循环值从10000000增加到1000000000,并在Release中再次运行以获得发布中的一些差异,结果如下:
你看到结果是不可能的 . 在某些情况下,使用Try / Catch的版本实际上更快!
我在紧密循环中测试了
try..catch
的实际影响,并且在任何正常情况下它本身都太小而不是性能问题 .如果循环工作很少(在我的测试中我做了
x++
),您可以测量异常处理的影响 . 具有异常处理的循环运行时间大约长十倍 .如果循环做了一些实际的工作(在我的测试中我调用了Int32.Parse方法),异常处理的影响太小而无法测量 . 我通过交换循环的顺序得到了更大的差异......
尝试catch块对性能的影响可以忽略但是异常投掷可能相当大,这可能是你的同事感到困惑的地方 .
理论上,try / catch块对代码行为没有影响,除非实际发生异常 . 然而,在一些罕见的情况下,try / catch块的存在可能会产生重大影响,而一些不常见但很难模糊的情况可能会引起注意 . 原因是给定的代码如下:
编译器可以根据statement2保证在statement3之前执行的事实来优化statement1 . 如果编译器可以识别出thing1没有副作用而且thing2实际上没有使用x,那么它可以安全地省略thing1 . 如果[在这种情况下] thing1是昂贵的,那可能是一个主要的优化,尽管thing1昂贵的情况也是编译器最不可能优化的情况 . 假设代码已更改:
现在存在一系列事件,其中statement3可以在没有执行statement2的情况下执行 . 即使
thing2
的代码中没有任何内容可以抛出异常,也可能是另一个线程可以使用Interlocked.CompareExchange
来注意q
被清除并将其设置为Thread.ResetAbort
,然后在statement2将其值写入x
之前执行Thread.Abort()
. 然后catch
将执行Thread.ResetAbort()
[通过委托q
],允许继续执行statement3 . 这样的事件序列当然是特别不可能的,但是编译器需要生成根据规范工作的代码,即使这些不可能的事件发生也是如此 .一般来说,编译器更有可能注意到遗漏简单代码的机会而不是复杂代码,因此如果永远不会抛出异常,try / catch很少会影响性能 . 尽管如此,在某些情况下,try / catch块的存在可能会阻止优化 - 但是对于try / catch - 会允许代码更快地运行 .
有关try / catch块如何工作的讨论,请参阅discussion on try/catch implementation,以及一些实现如何具有高开销,并且一些实现没有异常时的开销为零 . 特别是,我认为Windows 32位实现具有高开销,而64位实现则没有 .