315

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

我环顾四周,发现它会影响优化,但它似乎只适用于在作用域之间共享变量时。

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

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

4

13 回答 13

236

核实。

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
于 2009-08-20T20:02:06.370 回答
114

在查看了使用 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 行IL_0021: leave.s IL_002f。如果发生异常,控件跳转到行,IL_0025: ldloc.0否则我们跳转到标签IL_002d: leave.s IL_002f并函数返回。

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

于 2009-08-29T21:04:23.713 回答
73

不。如果 try/finally 块排除的琐碎优化实际上对您的程序有可衡量的影响,那么您可能一开始就不应该使用 .NET。

于 2009-08-20T20:00:25.733 回答
38

.NET 异常模型的相当全面的解释。

Rico Mariani 的性能花絮:异常成本:何时抛出,何时不抛出

第一种成本是在代码中进行异常处理的静态成本。托管异常在这里实际上做得比较好,我的意思是静态成本可能比 C++ 中的成本低得多。为什么是这样?好吧,静态成本确实在两种地方产生:第一,try/finally/catch/throw 的实际站点,其中有这些构造的代码。其次,在无人管理的代码中,跟踪所有在抛出异常时必须销毁的对象会产生隐蔽成本。必须存在大量的清理逻辑,而鬼鬼祟祟的部分是,即使是代码

德米特里·扎斯拉夫斯基:

根据 Chris Brumme 的说明:在存在 catch 的情况下,JIT 没有执行一些优化这一事实也会产生成本。

于 2009-08-20T20:00:10.197 回答
28

示例中的结构与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 配置有所不同。

这个版本,我注意到debug版本有区别(其实比其他版本多),但是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 的版本实际上更快!

于 2009-08-25T09:28:59.223 回答
16

我在一个紧密的循环中测试了 a 的实际影响try..catch,它本身太小了,在任何正常情况下都不会成为性能问题。

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

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

于 2009-08-20T20:14:49.527 回答
12

try catch 块对性能的影响可以忽略不计,但异常抛出可能相当大,这可能是您的同事感到困惑的地方。

于 2009-08-20T20:29:07.813 回答
11

尽管“预防胜于处理”,但从性能和效率的角度来看,我们可以选择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
于 2019-11-28T07:17:41.770 回答
7

try/catch 对性能有影响。

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

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

于 2009-08-29T19:51:24.407 回答
6

理论上,除非实际发生异常,否则 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不会引发异常,也有可能另一个线程可以使用 anInterlocked.CompareExchange来通知q已清除并将其设置为Thread.ResetAbort,然后执行Thread.Abort()before statement2 将其值写入x。然后catch将执行Thread.ResetAbort()[通过委托q],允许继续执行语句 3。这样的一系列事件当然是非常不可能发生的,但是即使发生这样的不可能事件,编译器也需要生成根据规范工作的代码。

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

于 2014-08-28T16:11:52.880 回答
5

是的,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 个字节。此外,每当编写 a 时,try/catch您很可能会有一个或多个throw new Exception("Message", ex)语句,进一步“膨胀”代码。

不过这里最重要的是代码内联.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>();
        }
    }
}
于 2020-10-06T15:42:51.583 回答
4

有关try/catch 块如何工作的讨论,以及一些实现如何在没有异常发生时具有高开销,而一些实现为零开销的讨论,请参见关于 try/catch 实现的讨论。特别是,我认为 Windows 32 位实现的开销很高,而 64 位实现则没有。

于 2009-08-31T08:20:53.777 回答
-2

我测试了一个深度尝试捕获。

        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
于 2021-08-19T12:04:54.123 回答