【问题标题】:Performance Cost Of 'try' in C#C# 中“尝试”的性能成本
【发布时间】:2010-10-26 09:43:15
【问题描述】:

我知道异常会降低性能,而且尝试避免异常通常比在所有事情上都放弃一个大的 try/catch 更有效——但是 try 块本身呢?仅仅声明一个 try/catch 的代价是什么,即使它从不抛出异常?

【问题讨论】:

  • 你是唯一能回答这个问题的人。你是唯一知道你使用什么样的硬件和软件的人,你是唯一知道哪些性能指标与你的客户相关的人,等等。写一些实际的基准,在真实的硬件上尝试,然后你就会知道答案。任何试图为您回答问题的人要么是在猜测,要么是在为他们的客户而非您的客户描述他们机器上的性能特征。无论哪种方式,这都不是您需要的数据。

标签: c# exception


【解决方案1】:

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

        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();

此代码的示例输出:

---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---

【讨论】:

    【解决方案2】:

    实际上,几个月前我正在创建一个 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());
    

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

    【讨论】:

    • 嗯。我刚刚在.Net 2.0 上试过这个(使用Stopwatch)。 20000 次循环迭代的 50000 次试验在没有 try-catch 的情况下需要 4184 毫秒,在 try-catch 情况下需要 4363 毫秒。这是一个非常小的差异。如果每次迭代实际上都在做一些简单的加法运算之外的事情,那么这种差异将更加不明显。我在调试和不调试的情况下都得到了类似的结果。
    【解决方案3】:

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

    展开堆栈实际上非常快——CLR 沿着调用堆栈向上走,只注意它找到的 finally 块;在纯 try-finally 块中,运行时绝不会尝试“完成”异常(它是元数据等)。

    据我所知,任何带有过滤器的 try-catch(例如“catch (FooException) {}”)都一样昂贵 - 即使它们对异常不做任何事情。

    我敢说一个方法(称为CatchesAndRethrows)具有以下块:

    try
    {
        ThrowsAnException();
    }
    catch
    {
        throw;
    }
    

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

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

    一些数字:

    With: 0.13905ms
    Without: 0.096ms
    Percent difference: 144%
    

    这是我运行的基准测试(记住,发布模式 - 无需调试即可运行):

        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();
        }
    

    【讨论】:

    • 我认为 OP 询问了不抛出异常时对性能的影响。
    【解决方案4】:

    您可能想阅读结构化异常处理。它是 Window 的异常实现,在 .NET 中使用。

    http://www.microsoft.com/msj/0197/Exception/Exception.aspx

    【讨论】:

      【解决方案5】:

      try 的性能开销很小。异常处理的主要成本是获取堆栈跟踪和其他元数据,这是在您实际必须抛出异常之前不会支付的成本。

      但这会因语言和实现而异。为什么不用 C# 编写一个简单的循环并自己计时?

      【讨论】:

      • 是的。 Try 实际上是免费的,因为它基本上是由元数据处理的,只有在实际抛出异常时才会检查。
      猜你喜欢
      • 2017-02-10
      • 1970-01-01
      • 2011-05-14
      • 2010-11-23
      • 2013-08-04
      • 2012-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多