在C#下,多少性能命中是try,throw和catch块

首先,免责声明:我有其他语言的经验,但我仍在学习C#的微妙之处

关于问题…我正在看一些代码,它以一种与我有关的方式使用try / catch块。 当调用解析例程而不是返回错误代码时,程序员使用以下逻辑

catch (TclException e) { throw new TclRuntimeError("unexpected TclException: " + e.Message,e); } 

这是由调用者捕获的,它会抛出同样的错误……
…被调用者捕获,抛出同样的错误……
…..这是由调用者捕获的,它会抛出同样的错误……

备份大约6个级别。

我认为所有这些catch / throw块都会导致性能问题,或者这是C#下的合理实现吗?

投掷(而不是捕获)是昂贵的。

除非你要做一些有用的事情(即转换为更有用的exception,处理错误),否则不要设置catch块。

只需重新抛出exception(不带参数的throw语句),或者更糟糕的是,抛出刚捕获的同一个对象绝对是错误的。

编辑:为避免歧义:

重新抛出:

 catch (SomeException) { throw; } 

从先前的exception对象创建exception,其中覆盖所有运行时提供的状态(特别是堆栈跟踪):

 catch (SomeException e) { throw e; } 

后一种情况是抛弃有关exception的信息的毫无意义的方法。 并且没有任何东西在捕获区块中投掷之前是毫无意义的。 可能更糟糕的是:

 catch (SomeException e) { throw new SomeException(e.Message); } 

它几乎丢失了所有包含的有用状态信息(包括最初抛出的信息)。

在任何语言下这都是一个糟糕的设计。

exception旨在捕获您可以处理它们的级别。 捕获exception,只是再次抛出exception只是浪费时间(这也会导致您丢失有关原始错误位置的有价值信息)。

显然,编写该代码的人曾经使用过错误代码,然后切换到exception,却没有真正了解它们的工作原理。 如果在一个级别没有捕获,则exception会自动“冒泡”堆栈。

另请注意,例外情况适用于特殊情况。 应该永远不会发生的事情。 它们不应该用于正常的有效性检查(即,不要捕获除零exception;检查预先除数是否为零)。

根据msdn:性能提示和技巧你可以使用try和catch而没有任何性能问题, 直到真正的抛出

通常,抛出exception在.NET中代价很高。 只是有一个try / catch / finally块不是。 所以,是的,现有代码从性能角度看是不好的,因为当它抛出时,它会抛出5-6个膨胀exception而不添加任何值而只是让原始exception自然地冒出5-6个堆栈帧。

更糟糕的是,从设计角度来看,现有代码非常糟糕。 exception处理的主要好处之一(与返回错误代码相比)是您无需在任何地方(在调用堆栈中)检查exception/返回代码。 您只需要在实际想要处理它们的少数几个地方捕获它们。 忽略exception(与忽略返回代码不同)不会忽略或隐藏问题。 它只是意味着它将在调用堆栈中处理得更高。

这本身并不可怕,但人们应该真正看到嵌套。

可接受的用例是这样的:

我是一个低级组件,可能会遇到许多不同的错误,但我的消费者只对特定类型的exception感兴趣。 因此我可以这样做:

 catch(IOException ex) { throw new PlatformException("some additional context", ex); } 

现在这允许消费者做:

 try { component.TryThing(); } catch(PlatformException ex) { // handle error } 

是的,我知道有些人会说,但消费者应该捕获IOException,但这取决于消费代码的实际抽象程度。 如果Impl将某些内容保存到磁盘并且消费者没有正当理由认为他们的操作会触及磁盘怎么办? 在这种情况下,将此exception处理放在使用代码中是没有意义的。

我们通常试图通过使用这种模式来避免的是在业务逻辑代码中放置一个“catch-all”exception处理程序,因为我们想要找出所有可能的exception类型,因为它们可能导致需要的更基本的问题调查。 如果我们没有捕获,它会冒泡,点击“顶级”级别的处理程序,并应该停止应用程序进一步。 这意味着客户报告该exception,您将有机会对其进行调查。 当您尝试构建健壮的软件时,这很重要。 您需要找到所有这些错误情况并编写特定代码来处理它们。

什么不是非常漂亮是嵌套这些过多的量,这是你应该用这个代码解决的问题。

正如另一张海报所说,例外情况属于合理的例外行为,但不要过于苛刻。 基本上代码应该表示“正常”操作,exception应该处理您可能遇到的潜在问题。

在性能方面,例外情况很好,如果你使用调试器对嵌入式设备进行测试,那么你会得到可怕的结果,但是在没有调试器的情况下,它们实际上非常快。

在讨论exception性能时,人们忘记的主要问题是,在错误情况下,一切都会因为用户遇到问题而减慢。 当网络瘫痪且用户无法保存工作时,我们真的关心速度吗? 我非常怀疑将错误报告更快地返回给用户几毫秒就会产生影响。

讨论exception时要记住的主要原则是exception不应出现在正常的应用程序流程中 (正常意味着没有错误)。 其他一切都源于这种说法。

在确切的例子中,你给我的不确定。 在我看来,在另一个通用的声音tclexception中包装看似普通的tclexception并没有带来任何好处。 如果有什么我会建议跟踪代码的原始创建者并了解他的思想背后是否有任何特定的逻辑。 有可能你可以杀掉捕获物。

Try / Catch / Throw很慢 – 一个更好的实现方法是在捕获它之前检查它,但是如果你绝对无法继续,那么你最好只在重要时投掷和捕获。 否则,检查和记录更有效。

如果堆栈的每一层只重新使用相同的信息重新生成相同的类型,则不添加任何新内容,那么这完全是荒谬的。

如果它发生在独立开发的图书馆之间的边界,那是可以理解的。 有时,库作者想要控制哪些exception从其库中逃脱,以便他们可以在以后更改其实现,而无需弄清楚如何模拟先前版本的exception抛出行为。

在没有充分理由的情况下,在任何情况下捕捉和重新投掷通常都是一个坏主意。 这是因为只要找到catch块,就会执行throw和catch之间的所有finally块。 只有在可以从中恢复exception时才会发生这种情况。 在这种情况下没关系,因为正在捕获特定类型,因此代码的作者(希望)知道他们可以安全地撤消任何自己的内部状态更改以响应该特定exception类型。

因此,那些try / catch块在设计时可能会产生成本 – 它们会使程序更加混乱。 但是在运行时,它们只会在抛出exception时施加大量成本,因为exception向上传输已经变得更加复杂。

例外情况很慢,尽量不要使用它们。

看到我在这里给出的答案。

基本上,Chris Brumme(曾在CLR团队工作过)表示他们是作为SEH例外实现的,因此当他们被抛出时你会受到重创,因为他们在OS堆栈中冒泡而不得不受到惩罚。 它是一篇非常优秀的文章 ,深入介绍了抛出exception时会发生的事情。 例如。:


当然,例外的最大成本是你实际抛出一个。 我将在博客结束时回到这里。


性能。 当您实际抛出并捕获exception时,exception会产生直接成本。 它们还可能具有与在方法输入上推送处理程序相关的间接成本。 而且他们通常可以通过限制codegen机会来获得潜在的成本。


但是,有一个严重的长期性能问题,例外,这必须考虑到您的决定。

考虑一下抛出exception时会发生的一些事情:

  • 通过解释编译器发出的元数据来获取堆栈跟踪,以指导我们的堆栈展开。

  • 在堆栈中运行一系列处理程序,每次调用每个处理程序两次。

  • 补偿SEH,C ++和托管exception之间的不匹配。

  • 分配托管的Exception实例并运行其构造函数。 最有可能的是,这涉及查找各种错误消息的资源。

  • 可能需要浏览一下OS内核。 经常采取硬件exception。

  • 通知任何附加的调试器,分析器,向量exception处理程序和其他感兴趣的方。

距离函数调用返回-1还有几年的时间。 例外本质上是非本地的,如果今天的架构有明显和持久的趋势,那么你必须保持本地性能以获得良好的性能。

有些人会声称exception不是问题,没有性能问题,通常是好事。 这些人获得了大量的民众投票,但他们完全错了。 我见过微软的工作人员提出了同样的要求(通常是通常为市场营销部门保留的技术知识),但马口的底线是谨慎使用它们。

旧的格言, exception只应用于特殊情况 ,对于C#和任何其他语言都是如此。