C#中try / catch的真正开销是多less?

所以,我知道try / catch确实增加了一些开销,因此不是一个控制stream程的好方法,但是这个开销是从哪里来的?它的实际影响是什么?

我并不是语言实现方面的专家(所以拿一点盐),但我认为最大的成本之一就是展开堆栈并将其存储为堆栈跟踪。 我怀疑只有当抛出exception(但我不知道)时才会发生这种情况,如果是这样的话,每次抛出exception时都会有大小不一的隐藏成本……所以不像你只是从一个地方在代码到另一个,有很多事情。

我不认为这是一个问题,只要你使用EXCEPTIONAL行为的例外(所以不是你通过程序的典型的,预期的path)。

这里要做的三点:

  • 首先,在你的代码中实际上有try-catch块时,性能会有很小的损失。 尝试避免在应用程序中使用它们时,不应该考虑这一点。 只有抛出exception时才会发挥性能。

  • 当除了其他人提到的栈展开操作等引发exception之外,还应该注意,为了填充exception类的成员,例如堆栈轨迹,会发生一大堆运行时/reflection相关的事情对象和各种types的成员等

  • 我相信这是为什么一般build议,如果你要重新抛出exception的原因之一就是throw; 而不是再次抛出exception或者构build一个新的exception,因为在那些情况下,所有的堆栈信息都被重新封装了,而在简单的抛出中则全部被保留下来。

你是问使用try / catch / finally的时候会不会抛出exception,或者是使用exception来控制stream程的开销? 后者有点类似于用一根炸药来点燃一个幼儿的生日蜡烛,而相关的开销则落在以下几个方面:

  • 由于所抛出的exception访问驻留数据,通常不会在caching中正常显示,因此可能会出现额外的caching未命中。
  • 由于抛出的exception访问非常驻代码和数据通常不会在您的应用程序的工作集中,您可以预期额外的页面错误。

    • 例如,抛出exception将要求CLR根据当前IP和每帧的返回IP来查找finally和catch块的位置,直到exception处理加上filter块为止。
    • 额外的build设成本和名称parsing,以创build用于诊断目的的框架,包括元数据的阅读等。
    • 上述两个项目通常访问“冷”的代码和数据,所以如果你有内存压力的话,硬页面错误是很可能的:

      • CLR会尝试将经常使用的数据所使用的代码和数据与经常使用的数据进行比较,以便改善局部性,因此,这会对您产生不利影响,因为您会迫使感冒发烫。
      • 硬页面错误的成本,如果有的话,将会使其他一切都变得渺茫。
  • 典型的捕捉情况往往很深,因此上述效果往往会被放大(增加页面错误的可能性)。

至于成本的实际影响,这可能会有很大的差异,取决于当时代码中还有哪些内容。 Jon Skeet 在这里有一个很好的总结 ,有一些有用的链接。 我倾向于同意他的观点,即如果您的例外情况严重损害了您的performance,那么您在使用例外情况方面遇到问题,而不仅仅是performance。

根据我的经验,最大的开销是实际抛出一个exception并处理它。 我曾经在一个项目中使用类似于下面的代码来检查是否有人有权编辑一些对象。 这个HasRight()方法在表示层的每个地方都被使用,并且经常被调用100个对象。

 bool HasRight(string rightName, DomainObject obj) { try { CheckRight(rightName, obj); return true; } catch (Exception ex) { return false; } } void CheckRight(string rightName, DomainObject obj) { if (!_user.Rights.Contains(rightName)) throw new Exception(); } 

当testing数据库充满了testing数据时,导致了一个非常明显的放缓,同时开辟新的forms等。

所以我把它重构为以下几点,根据之后的快速测量,速度大约快了两个数量级:

 bool HasRight(string rightName, DomainObject obj) { return _user.Rights.Contains(rightName); } void CheckRight(string rightName, DomainObject obj) { if (!HasRight(rightName, obj)) throw new Exception(); } 

因此,简而言之,在正常stream程中使用exception比使用类似stream程的stream程慢两个数量级。

更不用说,如果它是在一个经常调用的方法中,它可能会影响应用程序的整体行为。
例如,我认为在大多数情况下使用Int32.Parse是一个不好的做法,因为它会抛出exception,否则可以很容易地被捕获。

所以总结一下这里写的一切:
1)使用try..catch块来捕捉意外的错误 – 几乎没有性能损失。
2)如果可以避免的话,不要使用例外错误。

我之前写过一篇文章,因为当时有很多人在问这个问题。 你可以在http://www.blackwasp.co.uk/SpeedTestTryCatch.aspxfind它和testing代码。

结果是try / catch块的开销很小,但很小,应该被忽略。 但是,如果在执行数百万次的循环中运行try / catch块,则可能需要考虑将块移到循环外部。

try / catch块的关键性能问题是当你实际发生exception时。 这会给你的应用程序增加一个明显的延迟。 当然,当事情出错的时候,大多数开发人员(和许多用户)认识到暂停是一个即将发生的exception! 这里的关键是不要为正常的操作使用exception处理。 顾名思义,他们是特殊的,你应该尽一切可能避免被抛出。 您不应该将它们用作正常运行的程序的预期stream程的一部分。

去年,我做了一个关于这个主题的博客文章 。 一探究竟。 底线是,如果没有发生任何exception,几乎没有花费尝试块 – 在我的笔记本电脑上,一个例外是大约36μs。 这可能比你想象的要less,但请记住,那些结果在浅层叠加。 另外,第一个例外非常慢。

编写,debugging和维护没有编译器错误消息,代码分析警告消息和例程接受exception(特别是在一个地方抛出并在另一个地方被接受的exception)的代码要容易得多。 因为它更容易,代码平均会写得更好,更less的bug。

对我而言,程序员和质量开销是反对使用try-catch来处理stream程的主要理由。

相比之下,计算机的例外情况是微不足道的,在应用程序满足实际性能要求的能力方面通常很小。

我真的很喜欢Hafthor的博客文章 ,并且为这个讨论添加我的两分钱,我想说的是,让DATA LAYER只抛出一种types的exception(DataAccessException)总是很容易的。 这样我的业务层知道什么例外期望和捕捉它。 然后根据进一步的业务规则(即如果我的业务对象参与工作stream程等),我可能会抛出一个新的exception(BusinessObjectException)或继续进行而无需重新投掷。

我会说毫不犹豫地使用try..catch只要有必要,并明智地使用它!

例如,此方法参与工作stream程…

注释?

 public bool DeleteGallery(int id) { try { using (var transaction = new DbTransactionManager()) { try { transaction.BeginTransaction(); _galleryRepository.DeleteGallery(id, transaction); _galleryRepository.DeletePictures(id, transaction); FileManager.DeleteAll(id); transaction.Commit(); } catch (DataAccessException ex) { Logger.Log(ex); transaction.Rollback(); throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex); } } } catch (DbTransactionException ex) { Logger.Log(ex); throw new BusinessObjectException("Cannot delete gallery.", ex); } return true; } 

我们可以阅读Michael L. Scott编程语言语用学,认为当今的编译器不会在普通情况下增加任何开销,这意味着,当没有例外发生时。 所以每一项工作都是在编译时做出的。 但是当运行时抛出一个exception时,编译器需要执行二进制search才能find正确的exception,并且这将发生在每一次新的投掷中。

但是例外情况是例外情况,这个成本是完全可以接受 如果你试图做exception处理没有例外,并使用返回错误代码,可能你会需要一个if语句的每个子程序,这将产生真正的实时开销。 你知道一个if语句是否被转换成几个汇编指令,每当你进入子例程时都会执行。

对不起我的英文,希望能帮到你。 这些信息基于引用的书籍,更多信息请参阅第8.5章“exception处理”。

与普遍接受的理论相反, try / catch可能会对性能产生重大影响,那就是抛出exception了吗?

  1. 它会禁用某些自动优化(按devise) ,并且在某些情况下会注入debugging代码,如debugging助手所期望的那样。 在这一点上总会有人不同意我的观点,但是语言需要和反汇编来显示,所以这些人是通过字典定义妄想的 。
  2. 它会对维护造成负面影响。 这实际上是这里最重要的问题,但是因为我最后的回答(几乎完全集中在这个问题上)被删除了,所以我会试着把重点放在不那么重要的问题上(微观优化),而不是更重要的问题macros观优化)。

前几年,微软MVP已经发表了一些博客文章,我相信你可以很容易地find它们,但是StackOverflow对于内容非常关心所以我将提供一些链接作为填充证据:

  • try / catch / finally ( 和第二部分 )的性能影响 ,Peter Ritchie探讨了try / catch / finally禁用的优化(我将会继续引用标准中的引号)
  • 性能分析ParseTryParseConvertTo由Ian Huff公然指出:“exception处理非常缓慢”,并通过将Int.ParseInt.TryParse对抗来certificate这一点…任何坚持TryParse使用try / catch在幕后,这应该舍弃一些光!

也有这个答案 ,显示反汇编代码与 – 而不使用try / catch之间的区别。

看起来如此明显,在代码生成中存在一个公然可观的开销,而这种开销甚至似乎被微软所重视的人所承认! 然而我重复着互联网

是的,对于一个简单的代码行,有几十个额外的MSIL指令,甚至没有涵盖禁用的优化,所以在技术上它是一个微型优化。


几年前我发布了一个回答,因为它关注于程序员的生产力(macros观优化)。

这是不幸的,因为在这里和那里的CPU时间不能节省几个纳秒,可能会弥补人工手动优化的许多累积时间。 你的老板为此付出了多less:一小时的时间,或一小时的电脑运行? 我们在什么时候拔插头,承认现在是时候购买一台更快的电脑了

显然,我们应该优化我们的优先事项 ,而不仅仅是我们的代码! 在我最后的回答中,我借鉴了两段代码之间的差异。

使用try / catch

 int x; try { x = int.Parse("1234"); } catch { return; } // some more code here... 

不使用try / catch

 int x; if (int.TryParse("1234", out x) == false) { return; } // some more code here 

从维护开发人员的angular度来看,如果不是针对try / catch问题,更有可能浪费您的时间,如果不是在分析/优化(如上所述)的情况下可能甚至不需要的话,那么在通过源代码滚动…其中之一有四个额外的样板垃圾!

随着越来越多的领域被引入到一个类中,所有这些样板垃圾积累(源代码和反汇编代码)远远超出了合理的水平。 每场有四条线,而且它们总是一样的……我们没有被教导要避免重复自己吗? 我想我们可以隐藏一些自制的抽象背后的try / catch ,但是…那么我们可能只是避免exception(即使用Int.TryParse )。

这甚至不是一个复杂的例子。 我看到尝试在try / catch中实例化新类。 考虑到构造函数内部的所有代码都可能被取消某些优化的资格,否则编译器会自动应用这些优化。 产生编译器速度慢的理论,而不是编译器,有什么更好的方法正在按照要求去做

假设构造函数抛出了一个exception,并且引发了一些错误,那么这个可怜的维护开发人员就必须把它跟踪下来。 这可能不是一件容易的事情,不像goto梦魇的意大利面条代码, try / catch可能会导致三维的混乱,因为它可以向上移动到不仅仅是同一方法的其他部分,而且其他类和方法,所有这些都将由维护开发者来观察, 难的是 ! 然而我们被告知“goto是危险的”,嘿!

最后我提到, try / catch有它的好处,就是它被devise为禁用优化 ! 这是,如果你愿意, debugging援助 ! 这就是它的devise目的,它应该被用作…

我想这也是一个积极的一面。 它可以用来禁止优化,否则可能会削弱安全,multithreading应用程序的正常消息传递algorithm,并捕捉可能的竞争条件;)这是唯一的情况,我可以想到使用try / catch。 即使是有其他select。


什么优化trycatchfinally禁用?

AKA

如何trycatchfinally有用的debugging艾滋病?

他们是写障碍。 这来自标准:

12.3.3.13 Try-catch语句

对于以下forms的声明:

 try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n 
  • try-block开始处v的明确赋值状态与stmt开始处v的明确赋值状态相同。
  • catch-block-i (对于任何i )开头的v的明确赋值状态与stmt开头的v的明确赋值状态相同。
  • 如果(且仅当) v被明确地分配在try-block的结束点处,并且每个catch-block-i (对于从1到n的每个i ),则在stmt的结束点处v的明确分配状态是明确分配的)。

换句话说,在每个try语句的开头:

  • 在进入try语句之前对可见对象所做的所有赋值都必须是完整的,这需要线程锁启动,这对debugging竞态条件非常有用!
  • 编译器不允许:
    • 消除try语句之前已经分配的未使用的variables赋值
    • 重组或合并任何内部任务 (即,如果您还没有这样做,请参阅我的第一个链接)。
    • 在这个屏障上提升分配,延迟分配给它知道的variables,直到后来才使用(如果有的话),或者预先移动后面的分配以使其他优化成为可能…

每个catch声明都有类似的故事。 假设在你的try语句(或者它所调用的构造函数或函数等)中,你赋予了那个没有意义的variables(比如说, garbage=42; ),编译器不能消除这个语句,不pipe这个语句多么无关紧要可观察的程序行为。 在inputcatch块之前,分配需要完成

对于它的价值, finally讲述了一个同样有辱人格的故事:

12.3.3.14 Try-finally语句

对于一个try语句的formsstmt

 try try-block finally finally-block 

try-block开始处的v的明确赋值状态与stmt开始处的v的明确赋值状态相同。
finally块的起始处的v的明确赋值状态与stmt开始处的v的明确赋值状态相同。
•如果(并且只有)满足以下条件,则stmt的终点处的v的明确赋值状态被明确赋值:o vtry-block的终点明确赋值o v明确指定在finally-block如果一个控制stream转移(例如goto语句)在try-block内开始,并且在try-block之外结束,那么如果v明确地分配给v finally块的终点。 (这不是唯一的,如果 – 如果v在这个控制stream转移中被明确地分配了另一个原因,那么它仍然被认为是明确分配的。)

12.3.3.15 Try-catch-finally语句

trycatchfinally声明的forms的明确赋值分析:

 try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block 

就好像这个语句是一个tryfinally语句,里面包含trycatch语句:

 try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block 

让我们分析try / catch块最大可能的成本之一,在不需要使用的地方使用:

 int x; try { x = int.Parse("1234"); } catch { return; } // some more code here... 

这里是没有try / catch的那个:

 int x; if (int.TryParse("1234", out x) == false) { return; } // some more code here 

不计数不重要的空格,可能会注意到这两个相等的代码几乎是完全相同的字节长度。 后者包含4个字节减less缩进。 那是一件坏事?

为了增加伤害,学生决定循环,而input可以被parsing为int。 没有try / catch的解决scheme可能是这样的:

 while (int.TryParse(...)) { ... } 

但是,当使用try / catch时,这看起来如何?

 try { for (;;) { x = int.Parse(...); ... } } catch { ... } 

try / catch块是浪费缩进的神奇方式,我们甚至不知道它失败的原因! 想象一下,如果代码继续执行一个严重的逻辑缺陷,而不是停留在一个很好的明显的exception错误之上,那么进行debugging的人就会感觉如何。 Try / catch块是一个懒惰的人的数据validation/卫生。

其中一个较小的成本是try / catch块确实禁用了某些优化: http : //msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx 。 我想这也是一个积极的一面。 它可以用来禁止优化,否则可能会削弱安全,multithreading应用程序的正常消息传递algorithm,并捕捉可能的竞争条件;)这是唯一的情况,我可以想到使用try / catch。 即使是有其他select。