Kob*_*obi 261 c# performance try-catch
在与Microsoft员工进行代码审查期间,我们在try{}
块中遇到了大量代码.她和IT代表建议这可能会影响代码的性能.事实上,他们建议大多数代码应该在try/catch块之外,并且只应该检查重要的部分.微软员工补充说,即将发布的白皮书警告不要使用不正确的try/catch块.
我环顾四周,发现它可以影响优化,但它似乎只适用于范围之间共享变量.
我不是在询问代码的可维护性,甚至不是在处理正确的异常(有问题的代码需要重新分解,毫无疑问).我也没有提到使用流量控制的异常,这在大多数情况下显然是错误的.这些都是重要的问题(有些更重要),但不是重点.
如果不抛出异常,try/catch块如何影响性能?
Ben*_*n M 195
核实.
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);
}
Run Code Online (Sandbox Code Playgroud)
输出:
00:00:00.4269033 // with try/catch
00:00:00.4260383 // without.
Run Code Online (Sandbox Code Playgroud)
以毫秒为单位:
449
416
Run Code Online (Sandbox Code Playgroud)
新代码:
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();
}
Run Code Online (Sandbox Code Playgroud)
新结果:
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
Run Code Online (Sandbox Code Playgroud)
The*_*iot 101
在看到try/catch和没有try/catch的所有统计数据后,好奇心迫使我向后看,看看两个案例的生成情况.这是代码:
C#:
private static void TestWithoutTryCatch(){
Console.WriteLine("SIN(1) = {0} - No Try/Catch", Math.Sin(1));
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
C#:
private static void TestWithTryCatch(){
try{
Console.WriteLine("SIN(1) = {0}", Math.Sin(1));
}
catch (Exception ex){
Console.WriteLine(ex);
}
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我不是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
和函数返回.
我可以安全地假设,如果没有异常发生,那么创建局部变量以仅保存异常对象和跳转指令的开销.
Joh*_*ica 62
不可以.如果try/finally块排除的微不足道优化实际上对您的程序产生了可测量的影响,那么您可能不应该首先使用.NET.
aru*_*rul 34
Rico Mariani的表演花絮:例外成本:什么时候投掷,何时投注
第一种成本是在代码中进行异常处理的静态成本.托管异常实际上在这里做得比较好,我的意思是静态成本可以比C++中的低得多.为什么是这样?好吧,静态成本实际上是在两种情况下产生的:首先,try/finally/catch/throw的实际站点,其中有这些构造的代码.其次,在无人值守的代码中,存在与跟踪在抛出异常时必须被破坏的所有对象相关联的隐形成本.必须存在相当数量的清理逻辑,并且偷偷摸摸的部分是即使代码本身不会抛出或捕获或以其他方式明显使用异常仍然承担着知道如何清理自身的负担.
Dmitriy Zaslavskiy:
根据Chris Brumme的说明:还有一个成本与JIT在捕获存在时没有执行某些优化有关
awe*_*awe 25
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();
}
Run Code Online (Sandbox Code Playgroud)
当我从Ben M运行原始测试代码时,我注意到Debug和Releas配置都有区别.
这个版本,我注意到调试版本的差异(实际上比其他版本更多),但它在Release版本中没有区别.
结论:
基于这些测试,我认为我们可以说的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
Run Code Online (Sandbox Code Playgroud)
你看到结果是不可能的.在某些情况下,使用Try/Catch的版本实际上更快!
Guf*_*ffa 15
我try..catch
在一个紧凑的循环中测试了a的实际影响,并且在任何正常情况下它本身都太小而不是性能问题.
如果循环做的很少(在我的测试中我做了x++
),你可以测量异常处理的影响.具有异常处理的循环运行时间大约长十倍.
如果循环做了一些实际的工作(在我的测试中我调用了Int32.Parse方法),异常处理的影响太小而无法测量.我通过交换循环的顺序得到了更大的差异......
小智 9
虽然“预防胜于处理”,但从性能和效率的角度来看,我们可以选择try-catch而不是pre-variation。考虑以下代码:
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i < int.MaxValue; i++)
{
if (i != 0)
{
int k = 10 / i;
}
}
stopwatch.Stop();
Console.WriteLine($"With Checking: {stopwatch.ElapsedMilliseconds}");
stopwatch.Reset();
stopwatch.Start();
for (int i = 1; i < int.MaxValue; i++)
{
try
{
int k = 10 / i;
}
catch (Exception)
{
}
}
stopwatch.Stop();
Console.WriteLine($"With Exception: {stopwatch.ElapsedMilliseconds}");
Run Code Online (Sandbox Code Playgroud)
结果如下:
With Checking: 20367
With Exception: 13998
Run Code Online (Sandbox Code Playgroud)
是的,try/catch
会“损害”性能(一切都是相对的)。就浪费的CPU
周期而言并不多,但还有其他重要方面需要考虑:
首先,让我们使用一些复杂的工具(即BenchmarkDotNet )检查速度。编译为Release (AnyCPU)
,在机器上运行x64
。我想说没有什么区别,尽管测试确实会告诉我们这NoTryCatch()
要快一点点:
| Method | N | Mean | Error | StdDev |
|------------------ |---- |---------:|----------:|----------:|
| NoTryCatch | 0.5 | 3.770 ns | 0.0492 ns | 0.0411 ns |
| WithTryCatch | 0.5 | 4.060 ns | 0.0410 ns | 0.0384 ns |
| WithTryCatchThrow | 0.5 | 3.924 ns | 0.0994 ns | 0.0881 ns |
Run Code Online (Sandbox Code Playgroud)
一些附加说明。
| Method | Code size | Inlineable |
|------------------ |---------- |-----------:|
| NoTryCatch | 12 | yes |
| WithTryCatch | 18 | ? |
| WithTryCatchThrow | 18 | no |
Run Code Online (Sandbox Code Playgroud)
代码大小NoTryCatch()
产生 12 个字节的代码,而 try/catch 添加另外 6 个字节。另外,每当写一篇文章时,try/catch
您很可能会拥有一个或多个throw new Exception("Message", ex)
语句,从而进一步“膨胀”代码。
这里最重要的是代码内联。.NET
关键字的存在意味着throw
该方法永远不会被编译器内联(意味着代码速度较慢,但占用空间也更少)。我最近彻底测试了这个事实,所以它在.NET Core
. 不确定是否try/catch
遵循相同的规则。TODO: Verify!
| Method | N | Mean | Error | StdDev |
|------------------ |---- |---------:|----------:|----------:|
| NoTryCatch | 0.5 | 3.770 ns | 0.0492 ns | 0.0411 ns |
| WithTryCatch | 0.5 | 4.060 ns | 0.0410 ns | 0.0384 ns |
| WithTryCatchThrow | 0.5 | 3.924 ns | 0.0994 ns | 0.0881 ns |
Run Code Online (Sandbox Code Playgroud)
请参阅有关 try/catch 实现的讨论,了解有关 try/catch 块如何工作的讨论,以及在没有异常发生时某些实现如何具有高开销,而某些实现如何具有零开销。特别是,我认为Windows 32位实现的开销很高,而64位实现则不然。
理论上,除非实际发生异常,否则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
Run Code Online (Sandbox Code Playgroud)
编译器可能能够基于保证statement2在statement3之前执行的事实来优化statement1。如果编译器可以识别出thing1没有副作用并且thing2实际上没有使用x,则可以安全地完全省略thing1。如果thing1昂贵(如果是这种情况),那可能是一个主要的优化,尽管thing1昂贵的情况也是编译器最不可能优化的情况。假设代码已更改:
x=thing1(); // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x); // statement3
Run Code Online (Sandbox Code Playgroud)
现在存在一系列事件,其中statement3可以在不执行statement2的情况下执行。即使for的代码中没有任何东西thing2
可以引发异常,也有可能另一个线程可以使用an Interlocked.CompareExchange
来注意到q
已清除并将其设置为Thread.ResetAbort
,然后执行Thread.Abort()
before statement2将其值写入x
。然后,catch
将执行Thread.ResetAbort()
[通过委托q
],使执行继续执行statement3。这样的事件序列当然是极不可能发生的,但是即使发生了这种不太可能发生的事件,也需要编译器来生成根据规范工作的代码。
通常,与复杂的代码相比,编译器更有可能忽略掉一些简单的代码,因此,如果从未抛出异常,try / catch很少会影响性能。尽管如此,在某些情况下,try / catch块的存在可能会阻止优化,但对于try / catch而言,这种优化会使代码运行得更快。