试着抓住表现

时间:2009-08-29 02:30:22

标签: c# performance

MSDN上的

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

我在这里做错了什么,或者对此有合理的解释吗?

7 个答案:

答案 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秒,最好是更长时间,最好是几个小时。