C#中的例外有多贵?

How expensive are exceptions in C#?

C中的例外情况有多贵?似乎只要堆栈不深,它们就不会非常昂贵;但是我读过有冲突的报告。

是否有未被驳回的最终报告?


JonSkeet于2006年1月在.NET中编写了异常和性能

更新了异常和性能降低(谢谢@gulzar)

里科·马里亚尼(Rico Mariani)在.NET异常的真实成本中提出的建议--解决方案

另请参考:krzysztof cwalina-设计指南更新:异常引发


在阅读了异常在性能方面代价高昂的文章后,我提出了一个简单的测量程序,非常类似于几年前出版的乔恩·斯基特。我在这里提到这个主要是为了提供更新的数字。

程序在29914毫秒以下处理了100万个异常,相当于每毫秒33个异常。这足以使异常成为大多数情况下返回代码的可行替代方案。

但是,请注意,对于返回代码而不是异常,同一个程序的运行时间小于1毫秒,这意味着异常比返回代码慢至少30000倍。正如里科·马里亚尼所强调的,这些数字也是最小的数字。在实践中,抛出和捕获异常需要更多的时间。

在配备Intel Core2 Duo T8100@2,1 GHz的笔记本电脑上测量,该笔记本电脑的.NET 4.0发布版本不在调试器下运行(这会使其速度变慢)。

这是我的测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
static void Main(string[] args)
{
    int iterations = 1000000;
    Console.WriteLine("Starting" + iterations.ToString() +" iterations...
"
);

    var stopwatch = new Stopwatch();

    // Test exceptions
    stopwatch.Reset();
    stopwatch.Start();
    for (int i = 1; i <= iterations; i++)
    {
        try
        {
            TestExceptions();
        }
        catch (Exception)
        {
            // Do nothing
        }
    }
    stopwatch.Stop();
    Console.WriteLine("Exceptions:" + stopwatch.ElapsedMilliseconds.ToString() +" ms");

    // Test return codes
    stopwatch.Reset();
    stopwatch.Start();
    int retcode;
    for (int i = 1; i <= iterations; i++)
    {
        retcode = TestReturnCodes();
        if (retcode == 1)
        {
            // Do nothing
        }
    }
    stopwatch.Stop();
    Console.WriteLine("Return codes:" + stopwatch.ElapsedMilliseconds.ToString() +" ms");

    Console.WriteLine("
Finished."
);
    Console.ReadKey();
}

static void TestExceptions()
{
    throw new Exception("Failed");
}

static int TestReturnCodes()
{
    return 1;
}


我想我的观点是,如果异常的性能影响到您的应用程序,那么您就会抛出太多的异常。异常应适用于异常情况,而不是作为常规错误处理。

也就是说,我对异常处理方式的回忆基本上是在堆栈中查找与抛出的异常类型匹配的catch语句。因此,性能将受到来自catch的深度以及您有多少catch语句的影响最大。


在我的例子中,例外是非常昂贵的。我重写了这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public BlockTemplate this[int x,int y, int z]
{
    get
    {
        try
        {
            return Data.BlockTemplate[World[Center.X + x, Center.Y + y, Center.Z + z]];
        }
        catch(IndexOutOfRangeException e)
        {
            return Data.BlockTemplate[BlockType.Air];
        }
    }
}

进入这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public BlockTemplate this[int x,int y, int z]
{
    get
    {
        int ix = Center.X + x;
        int iy = Center.Y + y;
        int iz = Center.Z + z;
        if (ix < 0 || ix >= World.GetLength(0)
            || iy < 0 || iy >= World.GetLength(1)
            || iz < 0 || iz >= World.GetLength(2))
            return Data.BlockTemplate[BlockType.Air];
        return Data.BlockTemplate[World[ix, iy, iz]];
    }
}

并注意到一个良好的速度增长约30秒。此函数在启动时至少被调用32K次。代码并没有明确的意图是什么,但节省了大量的成本。


我做了自己的测量,以了解异常的含义有多严重。我没有尝试测量抛出/捕获异常的绝对时间。我最感兴趣的是,如果在每次传递中抛出异常,循环将变慢多少。测量代码如下

1
2
3
4
5
     for(; ; ) {
        iValue = Level1(iValue);
        lCounter += 1;
        if(DateTime.Now >= sFinish) break;
     }

VS

1
2
3
4
5
6
7
8
9
10
     for(; ; ) {
        try {
           iValue = Level3Throw(iValue);
        }
        catch(InvalidOperationException) {
           iValue += 3;
        }
        lCounter += 1;
        if(DateTime.Now >= sFinish) break;
     }

差别是20倍。第二个代码片段慢了20倍。


C中的准骨骼例外对象相当轻;通常是封装InnerException的能力,当对象树变得太深时,会使其变重。

至于最终的报告,我不知道有什么,尽管对于内存消耗和速度的粗略的点跟踪配置文件(或任何其他的配置文件)是相当容易做到的。


异常对性能的影响似乎是在生成异常对象的时候(尽管太小,90%的时候不会引起任何问题)。因此,建议对代码进行概要分析——如果异常导致性能下降,则编写一个不使用异常的新的高性能方法。(想到的一个例子是(通过使用异常的parse来解决性能问题,引入了triparse)

也就是说,在大多数情况下,异常不会在大多数情况下造成显著的性能损失-因此,MS设计指南是通过抛出异常来报告故障。