This文章指出,您可以根据需要使用尽可能多的try catch块,并且不会产生任何性能成本,因为不会引发任何实际异常。
因为我总是认为即使没有抛出异常,try-catch总是会受到很小的性能影响,所以我做了一点测试。
private void TryCatchPerformance()
{
int iterations = 100000000;
Stopwatch stopwatch = Stopwatch.StartNew();
int c = 0;
for (int i = 0; i < iterations; i++)
{
try
{
// c += i * (2 * (int)Math.Floor((double)i));
c += i * 2;
}
catch (Exception ex)
{
throw;
}
}
stopwatch.Stop();
WriteLog(String.Format("With try catch: {0}", stopwatch.ElapsedMilliseconds));
Stopwatch stopwatch2 = Stopwatch.StartNew();
int c2 = 0;
for (int i = 0; i < iterations; i++)
{
// c2 += i * (2 * (int)Math.Floor((double)i));
c2 += i * 2;
}
stopwatch2.Stop();
WriteLog(String.Format("Without try catch: {0}", stopwatch2.ElapsedMilliseconds));
}
我得到的输出:
With try catch: 68
Without try catch: 34
所以似乎没有使用try-catch块似乎更快了?
我发现更奇怪的是,当我用更复杂的东西替换for循环体中的计算时:c += i * (2 * (int)Math.Floor((double)i));
这种差异远远不那么显着
With try catch: 640
Without try catch: 655
我在这里做错了什么,或者对此有合理的解释吗?
答案 0 :(得分:15)
JIT不对'protected'/'try'块执行优化,我想根据你在try / catch块中编写的代码,这会影响你的性能。
答案 1 :(得分:14)
try / catch / finally / fault块本身在优化的发布程序集中基本上没有开销。虽然通常会为catch和finally块添加额外的IL,但是当没有抛出异常时,行为几乎没有差异。而不是一个简单的ret,通常有一个后来退休的假。
处理异常时会发生try / catch / finally块的真实成本。在这种情况下,必须创建异常,必须放置堆栈爬网标记,并且,如果处理异常并访问其StackTrace属性,则会发生堆栈遍历。最重的操作是堆栈跟踪,它跟随先前设置的堆栈爬网标记,以构建StackTrace对象,该对象可用于显示错误发生的位置以及它通过的调用。
如果try / catch块中没有任何行为,那么'leave to ret'与'ret'的额外成本将占主导地位,并且显然会有可衡量的差异。但是,在try子句中存在某种行为的任何其他情况下,块本身的成本将完全被否定。
答案 2 :(得分:6)
请注意,我只提供Mono:
// a.cs
public class x {
static void Main() {
int x = 0;
x += 5;
return ;
}
}
// b.cs
public class x {
static void Main() {
int x = 0;
try {
x += 5;
} catch (System.Exception) {
throw;
}
return ;
}
}
拆解这些:
// a.cs
default void Main () cil managed
{
// Method begins at RVA 0x20f4
.entrypoint
// Code size 7 (0x7)
.maxstack 3
.locals init (
int32 V_0)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: ldc.i4.5
IL_0004: add
IL_0005: stloc.0
IL_0006: ret
} // end of method x::Main
和
// b.cs
default void Main () cil managed
{
// Method begins at RVA 0x20f4
.entrypoint
// Code size 20 (0x14)
.maxstack 3
.locals init (
int32 V_0)
IL_0000: ldc.i4.0
IL_0001: stloc.0
.try { // 0
IL_0002: ldloc.0
IL_0003: ldc.i4.5
IL_0004: add
IL_0005: stloc.0
IL_0006: leave IL_0013
} // end .try 0
catch class [mscorlib]System.Exception { // 0
IL_000b: pop
IL_000c: rethrow
IL_000e: leave IL_0013
} // end handler 0
IL_0013: ret
} // end of method x::Main
我看到的主要区别是a.cs直接转到ret
IL_0006
,而b.cs转到leave IL_0013
IL_006
。我对我的示例的最佳猜测是leave
在编译为机器代码时是一个(相对)昂贵的跳转 - 可能是也可能不是这种情况,尤其是在你的for循环中。也就是说,try-catch没有固有的开销,但跳过 catch会有成本,就像任何条件分支一样。
答案 3 :(得分:4)
实际计算非常小,以至于准确的测量非常棘手。它看起来像try catch可能会增加一个非常小的固定数量的额外时间到例行程序。我不敢猜测,不知道C#中如何实现异常,这主要是异常路径的初始化,也许只是JIT的轻微负载。
对于任何实际使用,花在计算上的时间将压倒花费在try-catch上的时间,使得try-catch的成本可以接近零。
答案 4 :(得分:2)
问题首先出现在您的测试代码中。 你使用了秒表.Elapsed.Milliseconds ,它只显示了经过时间的毫秒部分, 使用 TotalMilliseconds 来获取整个部分......
如果没有抛出异常,则差异很小
但真正的问题是“我是否需要检查异常或让C#处理异常抛出?”
显然......单独处理...... 试试这个:
private void TryCatchPerformance()
{
int iterations = 10000;
textBox1.Text = "";
Stopwatch stopwatch = Stopwatch.StartNew();
int c = 0;
for (int i = 0; i < iterations; i++)
{
try
{
c += i / (i % 50);
}
catch (Exception)
{
}
}
stopwatch.Stop();
Debug.WriteLine(String.Format("With try catch: {0}", stopwatch.Elapsed.TotalSeconds));
Stopwatch stopwatch2 = Stopwatch.StartNew();
int c2 = 0;
for (int i = 0; i < iterations; i++)
{
int iMod50 = (i%50);
if(iMod50 > 0)
c2 += i / iMod50;
}
stopwatch2.Stop();
Debug.WriteLine( String.Format("Without try catch: {0}", stopwatch2.Elapsed.TotalSeconds));
}
输出: 已过时:请看下面的内容! 试试看:1.9938401
不试试:8.92E-05
很棒,只有10000个物体,有200个例外。
校正: 我在DEBUG和VS Written Exception上运行我的代码到输出窗口.. 这些是RELEASE的结果 开销要少得多,但仍有7,500%的改进。
使用try catch: 0.0546915
单独检查: 0.0007294
使用try catch抛出我自己的异常对象: 0.0265229
答案 5 :(得分:0)
有关try / catch块如何工作的讨论,请参阅discussion on try/catch implementation, 以及一些实现如何具有高开销,有些实现零开销, 当没有异常发生时。
答案 6 :(得分:0)
仅34毫秒的差异小于此类测试的误差范围。
正如您所注意到的那样,当您增加测试的持续时间时,差异就会消失,两组代码的性能实际上是相同的。
在进行此类基准测试时,我尝试将代码的每个部分循环至少20秒,最好是更长时间,最好是几个小时。