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

时间:2009-08-20 19:50:23

标签: c# performance try-catch

在与Microsoft员工进行代码审核期间,我们在try{}块中遇到了大量代码。她和IT代表建议这可能会影响代码的性能。事实上,他们建议大多数代码应该在try / catch块之外,并且只应该检查重要的部分。微软员工补充说,即将发布的白皮书警告不要使用不正确的try / catch块。

我环顾四周并发现它can affect optimizations,但它似乎只适用于范围之间共享变量。

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

抛出异常时,try / catch块如何影响性能?

12 个答案:

答案 0 :(得分:179)

检查一下。

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

答案 1 :(得分:92)

在看到try / catch和没有try / catch的所有统计数据后,好奇心迫使我在后面看以查看为这两种情况生成的内容。这是代码:

C#:

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

MSIL:

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

MSIL:

.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)上创建了一个本地异常对象,之后就像没有try / catch的方法一样,直到17行{{1 }}。如果发生异常,控件将跳转到第IL_0021: leave.s IL_002f行,否则我们会跳转到标签IL_0025: ldloc.0并返回函数。

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

答案 2 :(得分:56)

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

答案 3 :(得分:31)

Quite comprehensive explanation of the .NET exception model.

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

  

第一种成本是静态的   在中进行异常处理的成本   你的代码。管理异常   实际上在这里比较好,   我的意思是静态成本可以   远低于C ++中的说法。为什么是   这个?嗯,静态成本确实如此   在两种地方发生:   一,实际网站   try / finally / catch / throw在那里   这些结构的代码。第二,在   无人驾驶的代码,有隐身   与跟踪相关的成本   所有必须的对象   如果被破坏了   抛出异常。有个   相当多的清理逻辑   那必须存在和鬼鬼祟祟   部分是甚至没有的代码   本身扔或抓或其他   还有任何明显的例外情况   承担着如何做的负担   自己清理干净。

Dmitriy Zaslavskiy:

  

根据Chris Brumme的说明:有   还有与此事实有关的费用   一些优化没有   由JIT在场的情况下执行   捕获

答案 4 :(得分:22)

示例中的结构与 Ben M 不同。它将在内部for循环内部进行扩展,这将导致它在两种情况之间不能很好地进行比较。

以下更准确的比较,其中要检查的整个代码(包括变量声明)在Try / Catch块中:

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

当我从 Ben M 运行原始测试代码时,我注意到Debug和Releas配置都有区别。

这个版本,我注意到调试版本有所不同(实际上比其他版本更多),但它在Release版本中没有区别。

<强> 结论
根据这些测试,我认为我们可以说Try / Catch 确实对性能的影响很小。

修改
我试图将循环值从10000000增加到1000000000,并在Release中再次运行以获得发布中的一些差异,结果如下:

   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的版本实际上更快!

答案 5 :(得分:13)

我测试了try..catch在紧密循环中的实际影响,并且在任何正常情况下它都太小而无法成为性能问题。

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

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

答案 6 :(得分:10)

尝试catch块对性能的影响可以忽略但是异常投掷可能相当大,这可能是你的同事感到困惑的地方。

答案 7 :(得分:7)

try / catch HAS对性能的影响。

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

Here是关于try / catch性能的参考(虽然没有解释它的复杂性,但它暗示了)。请查看少抛出异常部分

答案 8 :(得分:4)

理论上,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

编译器可以根据statement2保证在statement3之前执行的事实来优化statement1。如果编译器能够识别出thing1没有副作用且thing2实际上没有使用x,那么它可以安全地省略thing1。如果[在这种情况下] thing1是昂贵的,那可能是一个主要的优化,虽然thing1昂贵的情况也是编译器最不可能优化的情况。假设代码已更改:

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

现在存在一系列事件,其中statement3可以在没有执行statement2的情况下执行。即使thing2的代码中没有任何内容可以抛出异常,另一个线程也可能会使用Interlocked.CompareExchange来注意q已被清除并将其设置为Thread.ResetAbort然后在statement2将其值写入Thread.Abort()之前执行x。然后catch将执行Thread.ResetAbort() [通过委托q],允许继续执行statement3。这样的事件序列当然是特别不可能的,但是编译器需要生成代码,即使发生这种不可能的事件,它也会按照规范工作。

一般来说,编译器更有可能注意到遗漏简单代码而不是复杂代码的机会,因此如果从不抛出异常,try / catch很少会影响性能。尽管如此,在某些情况下,try / catch块的存在可能会阻止优化 - 但是对于try / catch - 会使代码运行得更快。

答案 9 :(得分:3)

有关try / catch块如何工作的讨论,请参阅discussion on try/catch implementation,以及某些实现如何具有高开销,有些实现零开销, 当没有例外发生时。特别是,我认为Windows 32位实现具有高开销,而64位实现则没有。

答案 10 :(得分:3)

尽管“ 预防胜于处理”,但从性能和效率的角度来看,我们可以选择try-catch而不是pre-variation。考虑下面的代码:

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

答案 11 :(得分:2)

是的,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 |

代码大小NoTryCatch()在代码中产生12个字节,而try / catch则增加了6个字节。另外,每当编写try/catch时,您很可能会有一个或多个throw new Exception("Message", ex)语句,从而进一步使代码“膨胀”。

这里最重要的是代码 inline 。在.NET中,仅存在throw关键字就意味着该方法永远不会被编译器内联(这意味着代码更慢,而且占用空间更少)。我最近对此事实进行了彻底的测试,因此在.NET Core中似乎仍然有效。不知道try/catch是否遵循相同的规则。 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>();
        }
    }
}