在一次微软员工的代码检查中,我们在一个try{}块中发现了一大块代码。她和一位IT代表表示,这可能会对代码的性能产生影响。事实上,他们建议大部分代码应该在try/catch块之外,并且只检查重要的部分。这位微软员工补充说,即将发布的白皮书对错误的尝试/捕获块提出了警告。
我环顾四周,发现它会影响优化,但它似乎只适用于在作用域之间共享变量时。
我不是在问代码的可维护性,甚至不是在问如何处理正确的异常(毫无疑问,有问题的代码需要重构)。我也不是指使用异常进行流控制,这在大多数情况下显然是错误的。这些都是重要的问题(有些更重要),但不是这里的重点。
当不抛出异常时,try/catch块如何影响性能?
在本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和Releas配置中的差异。
这个版本,我注意到在调试版本中有一个不同(实际上比其他版本更多),但在发布版本中没有任何不同。
结论:
基于这些测试,我认为我们可以说Try/Catch确实对性能有很小的影响。
编辑:
我尝试将循环值从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的版本实际上更快!
在看到有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. init)上创建了一个局部异常对象。例外ex)之后的事情与没有try/catch的方法几乎相同,直到第17行IL_0021: leave。IL_002f。如果发生异常,则控件跳转到IL_0025: ldloc行。0否则跳转到标签IL_002d: leave。s IL_002f和函数返回。
我可以安全地假设,如果没有异常发生,那么创建局部变量只保存异常对象和跳转指令的开销。
. 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在现场执行
抓
检查它。
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