C#中"尝试"的性能成本

Mer*_*rus 21 c# exception

我知道异常会对性能造成损失,并且尝试避免异常通常比放弃一切大尝试/捕获更有效 - 但是try块本身呢?仅仅声明try/catch的成本是多少,即使它从不抛出异常?

JSB*_*ոգչ 27

尝试的性能成本非常小.异常处理的主要成本是获取堆栈跟踪和其他元数据,这是在您实际必须抛出异常之前未支付的成本.

但这取决于语言和实施方式.为什么不在C#中编写一个简单的循环并自己计时呢?


Car*_*rlo 7

实际上,几个月前我正在创建一个ASP.NET Web应用程序,我不小心用一个很长的循环包装了一个try/catch块.即使循环没有产生每个例外,但是花费了太多时间来完成.当我回去看到循环包裹的try/catch时,我反过来做了,我将循环包装在try/catch块中.性能提升很多.您可以自己尝试:做类似的事情

int total;

DateTime startTime = DateTime.Now;

for(int i = 0; i < 20000; i++)
{
try
{
total += i;
}
catch
{
// nothing to catch;
}
}

Console.Write((DateTime.Now - startTime).ToString());
Run Code Online (Sandbox Code Playgroud)

然后取出try/catch块.你会看到很大的不同!

  • 嗯.我刚试过.Net 2.0(使用`秒表`).20000次循环迭代的50000次试验需要4184ms没有"try-catch",4363ms带有"try-catch".这是一个非常小的差异.如果每次迭代实际上都在做一些超出简单加法操作的事情,那么这种差异将更不明显.无论有没有调试我都会得到类似的结果. (11认同)

Jon*_*son 5

一个常见的说法是,当它们被捕获时异常是昂贵的 - 而不是被抛出.这是因为大多数异常元数据收集(例如获取堆栈跟踪等)仅在try-catch端(不在throw端)发生.

展开堆栈实际上非常快 - CLR走上调用堆栈,只关注它找到的finally块; 在纯try-finally块中,运行时没有尝试"完成"异常(它的元数据等).

根据我的记忆,任何带过滤器的try-catches(例如"catch(FooException){}")都同样昂贵 - 即使它们没有对异常做任何事情.

我冒昧地用以下块说一个方法(称之为CatchesAndRethrows):

try
{
    ThrowsAnException();
}
catch
{
    throw;
}
Run Code Online (Sandbox Code Playgroud)

可能会导致方法中的堆栈遍历更快 - 例如:

try
{
    CatchesAndRethrows();
}
catch (Exception ex) // The runtime has already done most of the work.
{
    // Some fancy logic
}
Run Code Online (Sandbox Code Playgroud)

一些数字:

With: 0.13905ms
Without: 0.096ms
Percent difference: 144%
Run Code Online (Sandbox Code Playgroud)

这是我运行的基准测试(记住,发布模式 - 没有调试运行):

    static void Main(string[] args)
    {
        Stopwatch withCatch = new Stopwatch();
        Stopwatch withoutCatch = new Stopwatch();

        int iterations = 20000;

        for (int i = 0; i < iterations; i++)
        {
            if (i % 100 == 0)
            {
                Console.Write("{0}%", 100 * i / iterations);
                Console.CursorLeft = 0;
                Console.CursorTop = 0;
            }

            CatchIt(withCatch, withoutCatch);
        }

        Console.WriteLine("With: {0}ms", ((float)(withCatch.ElapsedMilliseconds)) / iterations);
        Console.WriteLine("Without: {0}ms", ((float)(withoutCatch.ElapsedMilliseconds)) / iterations);
        Console.WriteLine("Percent difference: {0}%", 100 * withCatch.ElapsedMilliseconds / withoutCatch.ElapsedMilliseconds);
        Console.ReadKey(true);
    }

    static void CatchIt(Stopwatch withCatch, Stopwatch withoutCatch)
    {
        withCatch.Start();

        try
        {
            FinallyIt(withoutCatch);
        }
        catch
        {
        }

        withCatch.Stop();
    }

    static void FinallyIt(Stopwatch withoutCatch)
    {
        try
        {
            withoutCatch.Start();
            ThrowIt(withoutCatch);
        }
        finally
        {
            withoutCatch.Stop();
        }
    }

    private static void ThrowIt(Stopwatch withoutCatch)
    {
        throw new NotImplementedException();
    }
Run Code Online (Sandbox Code Playgroud)


fiL*_*net 5

要查看其实际成本,可以运行以下代码。它采用简单的二维数组并生成超出范围的随机坐标。如果您的例外仅发生一次,那么您当然不会注意到它。我的示例旨在强调执行数千次操作的含义,以及捕获异常与实施简单测试可以节省您的时间。

        const int size = 1000;
        const int maxSteps = 100000;

        var randomSeed = (int)(DateTime.UtcNow - new DateTime(1970,1,1,0,0,0).ToLocalTime()).TotalMilliseconds;
        var random = new Random(randomSeed);
        var numOutOfRange = 0;
        var grid = new int[size,size];
        var stopwatch = new Stopwatch();
        Console.WriteLine("---Start test with exception---");
        stopwatch.Reset();
        stopwatch.Start();
        for (int i = 0; i < maxSteps; i++)
        {
            int coord = random.Next(0, size * 2);
            try
            {
                grid[coord, coord] = 1;
            }
            catch (IndexOutOfRangeException)
            {
                numOutOfRange++;
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Time used: " + stopwatch.ElapsedMilliseconds + "ms, Number out of range: " + numOutOfRange);
        Console.WriteLine("---End test with exception---");

        random = new Random(randomSeed);

        stopwatch.Reset();
        Console.WriteLine("---Start test without exception---");
        numOutOfRange = 0;
        stopwatch.Start();
        for (int i = 0; i < maxSteps; i++)
        {
            int coord = random.Next(0, size * 2);
            if (coord >= grid.GetLength(0) || coord >= grid.GetLength(1))
            {
                numOutOfRange++;
                continue;
            }
            grid[coord, coord] = 1;
        }
        stopwatch.Stop();
        Console.WriteLine("Time used: " + stopwatch.ElapsedMilliseconds + "ms, Number out of range: " + numOutOfRange);
        Console.WriteLine("---End test without exception---");
        Console.ReadLine();
Run Code Online (Sandbox Code Playgroud)

此代码的示例输出:

---Start test with exception---
Time used: 3228ms, Number out of range: 49795
---End test with exception---
---Start test without exception---
Time used: 3ms, Number out of range: 49795
---End test without exception---
Run Code Online (Sandbox Code Playgroud)