代码之家  ›  专栏  ›  技术社区  ›  Kobi

当没有抛出异常时,try/catch块是否会影响性能?

  •  228
  • Kobi  · 技术社区  · 17 年前

    在与一名微软员工进行代码审查时,我们在 try{}

    我到处找了找,找到了 can affect optimizations ,但它似乎只适用于在作用域之间共享变量的情况。

    我不是在问代码的可维护性,甚至不是在问如何处理正确的异常(毫无疑问,有问题的代码需要重新分解)。我也不是指在流控制中使用异常,这在大多数情况下显然是错误的。这些都是重要的问题(有些更重要),但不是这里的重点。

    当出现异常时,try/catch块如何影响性能 不 扔?

    10 回复  |  直到 7 年前
        1
  •  232
  •   Anatoliy Nikolaev    12 年前

    检查一下。

    static public void Main(string[] args)
    {
        Stopwatch w = new Stopwatch();
        double d = 0;
    
        w.Start();
    
        for (int i = 0; i < 10000000; i++)
        {
            try
            {
                d = Math.Sin(1);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    
        w.Stop();
        Console.WriteLine(w.Elapsed);
        w.Reset();
        w.Start();
    
        for (int i = 0; i < 10000000; i++)
        {
            d = Math.Sin(1);
        }
    
        w.Stop();
        Console.WriteLine(w.Elapsed);
    }
    

    00:00:00.4269033  // with try/catch
    00:00:00.4260383  // without.
    

    以毫秒为单位:

    449
    416
    

    for (int j = 0; j < 10; j++)
    {
        Stopwatch w = new Stopwatch();
        double d = 0;
        w.Start();
    
        for (int i = 0; i < 10000000; i++)
        {
            try
            {
                d = Math.Sin(d);
            }
    
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
    
            finally
            {
                d = Math.Sin(d);
            }
        }
    
        w.Stop();
        Console.Write("   try/catch/finally: ");
        Console.WriteLine(w.ElapsedMilliseconds);
        w.Reset();
        d = 0;
        w.Start();
    
        for (int i = 0; i < 10000000; i++)
        {
            d = Math.Sin(d);
            d = Math.Sin(d);
        }
    
        w.Stop();
        Console.Write("No try/catch/finally: ");
        Console.WriteLine(w.ElapsedMilliseconds);
        Console.WriteLine();
    }
    

    新结果:

       try/catch/finally: 382
    No try/catch/finally: 332
    
       try/catch/finally: 375
    No try/catch/finally: 332
    
       try/catch/finally: 376
    No try/catch/finally: 333
    
       try/catch/finally: 375
    No try/catch/finally: 330
    
       try/catch/finally: 373
    No try/catch/finally: 329
    
       try/catch/finally: 373
    No try/catch/finally: 330
    
       try/catch/finally: 373
    No try/catch/finally: 352
    
       try/catch/finally: 374
    No try/catch/finally: 331
    
       try/catch/finally: 380
    No try/catch/finally: 329
    
       try/catch/finally: 374
    No try/catch/finally: 334
    
        2
  •  114
  •   TheVillageIdiot    17 年前

    在…的后面 查看为这两种情况生成的内容。代码如下:

    C#:

    private static void TestWithoutTryCatch(){
        Console.WriteLine("SIN(1) = {0} - No Try/Catch", Math.Sin(1)); 
    }
    

    .method private hidebysig static void  TestWithoutTryCatch() cil managed
    {
      // Code size       32 (0x20)
      .maxstack  8
      IL_0000:  nop
      IL_0001:  ldstr      "SIN(1) = {0} - No Try/Catch"
      IL_0006:  ldc.r8     1.
      IL_000f:  call       float64 [mscorlib]System.Math::Sin(float64)
      IL_0014:  box        [mscorlib]System.Double
      IL_0019:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                    object)
      IL_001e:  nop
      IL_001f:  ret
    } // end of method Program::TestWithoutTryCatch
    

    C#:

    private static void TestWithTryCatch(){
        try{
            Console.WriteLine("SIN(1) = {0}", Math.Sin(1)); 
        }
        catch (Exception ex){
            Console.WriteLine(ex);
        }
    }
    

    .method private hidebysig static void  TestWithTryCatch() cil managed
    {
      // Code size       49 (0x31)
      .maxstack  2
      .locals init ([0] class [mscorlib]System.Exception ex)
      IL_0000:  nop
      .try
      {
        IL_0001:  nop
        IL_0002:  ldstr      "SIN(1) = {0}"
        IL_0007:  ldc.r8     1.
        IL_0010:  call       float64 [mscorlib]System.Math::Sin(float64)
        IL_0015:  box        [mscorlib]System.Double
        IL_001a:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                      object)
        IL_001f:  nop
        IL_0020:  nop
        IL_0021:  leave.s    IL_002f //JUMP IF NO EXCEPTION
      }  // end .try
      catch [mscorlib]System.Exception 
      {
        IL_0023:  stloc.0
        IL_0024:  nop
        IL_0025:  ldloc.0
        IL_0026:  call       void [mscorlib]System.Console::WriteLine(object)
        IL_002b:  nop
        IL_002c:  nop
        IL_002d:  leave.s    IL_002f
      }  // end handler
      IL_002f:  nop
      IL_0030:  ret
    } // end of method Program::TestWithTryCatch
    

    我不是IL方面的专家,但我们可以看到在第四行创建了一个本地异常对象 .locals init ([0] class [mscorlib]System.Exception ex) IL_0021: leave.s IL_002f . 如果发生异常,控件将跳转到第行 IL_0025: ldloc.0 否则我们跳到标签上 IL_002d: leave.s IL_002f 和函数返回。

    我可以安全地假设,如果没有异常发生,那么创建本地变量来保存异常对象的开销就很大 只有 和跳转指令。

        3
  •  72
  •   John Kugelman Michael Hodel    17 年前

    不。如果try/finally块排除的琐碎优化实际上对您的程序产生了可测量的影响,那么您可能不应该首先使用.NET。

        4
  •  37
  •   Robert Cartaino    17 年前

    Quite comprehensive explanation of the .NET exception model.

    Rico Mariani的表演花絮: Exception Cost: When to throw and when not to

    在中进行异常处理的成本 其实在这里做得比较好,, 我的意思是静态成本可以是 比C++中的要低很多。为什么 这嗯,静态成本真的很高 发生在两种地方: 首先,有关的实际地点 这些构造的代码。第二,在 无管理代码,这是秘密 必须删除的所有对象 如果 异常被抛出。有一个 大量的清理逻辑 是否仍然公开使用异常 承担着知道如何 收拾干净。

    根据Chris Brumme的注释:有 一些优化没有被实现 抓住

        5
  •  28
  •   awe    17 年前

    示例中的结构与 本·M . 它将延伸到内部的顶部 for 循环,这将导致两种情况之间的比较不好。

            for (int j = 0; j < 10; j++)
            {
                Stopwatch w = new Stopwatch();
                w.Start();
                try { 
                    double d1 = 0; 
                    for (int i = 0; i < 10000000; i++) { 
                        d1 = Math.Sin(d1);
                        d1 = Math.Sin(d1); 
                    } 
                }
                catch (Exception ex) {
                    Console.WriteLine(ex.ToString()); 
                }
                finally { 
                    //d1 = Math.Sin(d1); 
                }
                w.Stop(); 
                Console.Write("   try/catch/finally: "); 
                Console.WriteLine(w.ElapsedMilliseconds); 
                w.Reset(); 
                w.Start(); 
                double d2 = 0; 
                for (int i = 0; i < 10000000; i++) { 
                    d2 = Math.Sin(d2);
                    d2 = Math.Sin(d2); 
                } 
                w.Stop(); 
                Console.Write("No try/catch/finally: "); 
                Console.WriteLine(w.ElapsedMilliseconds); 
                Console.WriteLine();
            }
    

    当我从中运行原始测试代码时 ,我注意到调试和Releas配置都有差异。

    在这个版本中,我注意到调试版本中存在差异(实际上比其他版本更大),但在发布版本中没有差异。

    :
    基于这些测试,我认为我们可以说Try/Catch 对性能的影响很小。

    编辑:

       try/catch/finally: 509
    No try/catch/finally: 486
    
       try/catch/finally: 479
    No try/catch/finally: 511
    
       try/catch/finally: 475
    No try/catch/finally: 477
    
       try/catch/finally: 477
    No try/catch/finally: 475
    
       try/catch/finally: 475
    No try/catch/finally: 476
    
       try/catch/finally: 477
    No try/catch/finally: 474
    
       try/catch/finally: 475
    No try/catch/finally: 475
    
       try/catch/finally: 476
    No try/catch/finally: 476
    
       try/catch/finally: 475
    No try/catch/finally: 476
    
       try/catch/finally: 475
    No try/catch/finally: 474
    

    你看,结果是不合理的。在某些情况下,使用Try/Catch的版本实际上更快!

        6
  •  16
  •   Guffa    17 年前

    我测试了 try..catch 它本身太小,在任何正常情况下都不会成为性能问题。

    如果循环做的工作很少(在我的测试中,我做了一个 x++ ),您可以度量异常处理的影响。带有异常处理的循环运行的时间大约要长十倍。

    如果循环做了一些实际工作(在我的测试中,我称为Int32.Parse方法),异常处理的影响太小,无法测量。通过交换循环的顺序,我得到了更大的差异。。。

        7
  •  12
  •   RHicke    17 年前

        8
  •  10
  •   mayo    6 年前

    尽管” 预防胜于处理

    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    for (int i = 1; i < int.MaxValue; i++)
    {
        if (i != 0)
        {
            int k = 10 / i;
        }
    }
    stopwatch.Stop();
    Console.WriteLine($"With Checking: {stopwatch.ElapsedMilliseconds}");
    stopwatch.Reset();
    stopwatch.Start();
    for (int i = 1; i < int.MaxValue; i++)
    {
        try
        {
            int k = 10 / i;
        }
        catch (Exception)
        {
    
        }
    }
    stopwatch.Stop();
    Console.WriteLine($"With Exception: {stopwatch.ElapsedMilliseconds}");
    

    结果如下:

    With Checking:  20367
    With Exception: 13998
    
        9
  •  8
  •   Isaac    15 年前

    try/catch对性能有影响。

    但这并不是一个巨大的影响。try/catch的复杂度通常是O(1),就像一个简单的赋值一样,除非它们被放置在循环中。所以你必须明智地使用它们。

    Here 是关于try/catch性能的参考(虽然没有解释它的复杂性,但它是隐含的)。看看 部分

        10
  •  6
  •   supercat    12 年前

    理论上,try/catch块不会对代码行为产生影响,除非实际发生异常。然而,在一些罕见的情况下,try/catch块的存在可能会产生重大影响,而在一些不常见但几乎不模糊的情况下,这种影响可能是显而易见的。原因是给定的代码如下所示:

    Action q;
    double thing1()
      { double total; for (int i=0; i<1000000; i++) total+=1.0/i; return total;}
    double thing2()
      { q=null; return 1.0;}
    ...
    x=thing1();     // statement1
    x=thing2(x);    // statement2
    doSomething(x); // statement3
    

    x=thing1();      // statement1
    try
    { x=thing2(x); } // statement2
    catch { q(); }
    doSomething(x);  // statement3
    

    现在存在一系列事件,其中statement3可以在没有执行statement2的情况下执行。即使代码中没有关于 thing2 Interlocked.CompareExchange 注意到 q 已清除并将其设置为 Thread.ResetAbort ,然后执行 Thread.Abort() 在语句2将其值写入之前 x . 然后 catch 将执行 Thread.ResetAbort() [通过代表 Q ],允许继续执行语句3。这样的事件序列当然是极不可能的,但编译器需要生成代码,即使在发生这种不可能的事件时也能根据规范工作。

        11
  •  4
  •   Community Mohan Dere    9 年前

    看见 discussion on try/catch implementation 当没有异常发生时。特别是,我认为Windows 32位实现有很高的开销,而64位实现没有。

        12
  •  3
  •   l33t    6 年前

    对 try/catch CPU 周期,但还有其他重要方面需要考虑:

    • 方法内联

    基准

    首先,让我们使用一些复杂的工具(例如。 BenchmarkDotNet ).汇编为 Release (AnyCPU) ,继续 x64 NoTryCatch()

    |            Method |   N |     Mean |     Error |    StdDev |
    |------------------ |---- |---------:|----------:|----------:|
    |        NoTryCatch | 0.5 | 3.770 ns | 0.0492 ns | 0.0411 ns |
    |      WithTryCatch | 0.5 | 4.060 ns | 0.0410 ns | 0.0384 ns |
    | WithTryCatchThrow | 0.5 | 3.924 ns | 0.0994 ns | 0.0881 ns |
    

    分析

    一些补充说明。

    |            Method | Code size | Inlineable |
    |------------------ |---------- |-----------:|
    |        NoTryCatch |        12 |        yes |
    |      WithTryCatch |        18 |          ? |
    | WithTryCatchThrow |        18 |         no |
    

    代码大小 在代码中产生12个字节,而try/catch再增加6个字节。而且,每当写一篇文章 您很可能会有一个或多个 throw new Exception("Message", ex) 语句,进一步“膨胀”代码。

    内联 . 在里面 .NET 仅仅存在 throw 关键字意味着编译器永远不会内联该方法(这意味着代码速度较慢,但占用空间也较小)。我最近彻底地测试了这个事实,所以它在中国仍然有效 .NET Core . 不确定是否 遵循同样的规则。 TODO: Verify!

    完整测试代码

    using System;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Running;
    
    namespace TryCatchPerformance
    {
        public class TryCatch
        {
            [Params(0.5)]
            public double N { get; set; }
    
            [Benchmark]
            public void NoTryCatch() => Math.Sin(N);
    
            [Benchmark]
            public void WithTryCatch()
            {
                try
                {
                    Math.Sin(N);
                }
                catch
                {
                }
            }
    
            [Benchmark]
            public void WithTryCatchThrow()
            {
                try
                {
                    Math.Sin(N);
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                var summary = BenchmarkRunner.Run<TryCatch>();
            }
        }
    }
    
        13
  •  -2
  •   Snowy    5 年前

    我试了一次深捕。

            static void TryCatch(int level, int max)
            {
                try
                {
                    if (level < max) TryCatch(level + 1, max);
                }
                catch
                { }
            }
            static void NoTryCatch(int level, int max)
            {
                if (level < max) NoTryCatch(level + 1, max);
            }
            static void Main(string[] args)
            {
                var s = new Stopwatch();
                const int max = 10000;
                s.Start();
                TryCatch(0, max);
                s.Stop();
                Console.WriteLine("try-catch " + s.Elapsed);
                s.Restart();
                NoTryCatch(0, max);
                s.Stop();
                Console.WriteLine("no try-catch " + s.Elapsed);
            }
    

    结果是:

    try-catch 00:00:00.0008528
    no try-catch 00:00:00.0002422