为什么尝试块昂贵?
我已经听到了build议,如果可能的话,你应该避免尝试catch块,因为它们很贵。
我的问题是关于.NET平台:为什么尝试块昂贵?
回应摘要:
在这个问题上显然有两个阵营:那些说试块的代价是昂贵的,而那些说“也许是一点点”的。
那些说try块是昂贵的,通常提到解除调用堆栈的“高成本”。 就我个人而言,我并不相信这个说法 – 特别是在阅读了这里存储exception处理程序之后。
Jon Skeet坐在“也许是一点点”的阵营,写了两篇关于例外和表演的文章,你可以在这里find。
有一篇文章我觉得非常有趣:它讨论了try块的“其他”性能影响(不一定是内存或cpu消耗)。 Peter Ritchie提到他发现try块内部的代码没有被编译器优化。 你可以在这里阅读他的发现。
最后,还有一个关于在CLR中实现exception的人的问题的博客条目。 去看看Chris Brumme的文章吧。
这不是块本身,而是昂贵的,它甚至没有捕获exception,本身,这是昂贵的,它是运行时展开调用堆栈,直到find一个可以处理exception的堆栈帧。 抛出一个exception是相当轻量级的,但是如果运行时必须遍历六个堆栈框架(即六个深度的方法调用)才能find一个合适的exception处理程序,可能最后执行块,您可能会看到明显的时间。
您不应该避免try / catch块,因为这通常意味着您没有正确处理可能发生的exception。 结构化exception处理(SEH)只有在实际发生exception时才是昂贵的,因为运行时必须遍历调用堆栈寻找捕获处理程序,执行该处理程序(可能有多个),然后执行finally块,然后返回控制回在正确的位置的代码。
exception不是用来控制程序逻辑,而是用来指示错误状态。
关于例外的最大误解之一是它们是为了“特殊的条件”。事实是,它们是用于沟通错误条件的。 从框架devise的angular度来看,并不存在“特殊情况”。 一个条件是否是例外取决于使用的上下文,但可重用的库很less知道它们将如何使用。 例如,对于简单的数据input应用程序,OutOfMemoryException可能是例外; 对于执行自己的内存pipe理的应用程序(例如SQL服务器)来说并不是那么例外。 换句话说,一个人的特殊状况是另一个人的慢性病。 [http://blogs.msdn.com/kcwalina/archive/2008/07/17/ExceptionalError.aspx]
一个try块并不昂贵。 除非抛出exception,否则几乎没有成本发生。 如果抛出exception,这是一个特殊的情况,你不再关心性能。 如果您的程序需要0.001秒或1.0秒才能完成,那么这很重要吗? 不,不是的。 重要的是,向你报告的信息有多好,你可以修复它,并阻止它再次发生。
我认为人们真的高估了抛出exception的性能成本。 是的,性能受到影响,但相对较小。
我跑了下面的testing,抛出了100万个例外。 在我的英特尔酷睿2双核 2.8 GHz上花了大约20秒。 那大概是每秒5万个例外。 如果你扔了一小部分,你会遇到一些架构问题。
这是我的代码:
using System; using System.Diagnostics; namespace Test { class Program { static void Main(string[] args) { Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 1000000; i++) { try { throw new Exception(); } catch {} } Console.WriteLine(sw.ElapsedMilliseconds); Console.Read(); } } }
当您将代码包装在try / catch块中时,编译器会发出更多的IL; 看下面的程序:
using System; public class Program { static void Main(string[] args) { Console.WriteLine("abc"); } }
编译器会发出这个IL:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "abc" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method Program::Main
虽然略有修改版本:
using System; public class Program { static void Main(string[] args) { try { Console.WriteLine("abc"); } catch { } } }
排放更多:
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 23 (0x17) .maxstack 1 IL_0000: nop .try { IL_0001: nop IL_0002: ldstr "abc" IL_0007: call void [mscorlib]System.Console::WriteLine(string) IL_000c: nop IL_000d: nop IL_000e: leave.s IL_0015 } // end .try catch [mscorlib]System.Object { IL_0010: pop IL_0011: nop IL_0012: nop IL_0013: leave.s IL_0015 } // end handler IL_0015: nop IL_0016: ret } // end of method Program::Main
所有这些NOP和其他成本。
海事组织这整个讨论就像说:“哇,因为我需要增加一个柜台,我不会再使用它们了”,或者“创build一个对象需要时间,我不打算创build一吨物体“。
底线是你的添加代码,大概是有原因的。 如果代码行没有引起一些开销,即使它的1个CPU周期,那么它为什么会存在? 没有什么是免费的
明智的做法,就像添加到应用程序中的任何代码行一样,只有在需要执行某些操作时才会将其放在那里。 如果捕获一个exception是你需要做的事情,那就这样做吧,就像你需要一个string来存储一些东西,创build一个新的string一样。 通过同样的方法,如果你声明一个以前没有使用的variables,那么你正在浪费内存和CPU周期来创build它,并且应该被删除。 与try / catch一样。
换句话说,如果有代码需要做某些事情,那么假定做某事将以某种方式消耗CPU和/或内存。
这不是尝试块,你需要担心的一样多catch块。 然后,不是你想避免写块:这是你想要尽可能写代码,永远不会实际使用它们。
我怀疑他们是否特别贵。 很多时候,他们是必要/必需的。
尽pipe我强烈build议只在必要的时候使用它们,并且在正确的位置/层次上嵌套,而不是在每次调用返回时重新抛出exception。
我会想象这个build议的主要原因是说你不应该使用try-catches,否则这将是一个更好的方法。
这不是我永远不会担心的事情。 我宁愿关心一个尝试的清晰度和安全性……最后会阻止关于自己“多么昂贵”的问题。
我个人不使用286,也没有人使用.NET或Java。 继续。 担心编写好的代码会影响你的用户和其他开发者,而不是那些使用它的人99.999999%正常工作的底层框架。
这可能不是很有帮助,我并不是要尖刻,而是突出视angular。
稍微O / T,但…
有一个相当好的devise理念,说你永远不应该需要exception处理。 这意味着您应该能够查询任何对象,以查找可能会抛出exception的任何条件。
就像能够在“write()”之前说“writable()”一样。
这是一个相当不错的想法,如果使用,它会使Java中检查exception看起来很愚蠢 – 我的意思是,检查一个条件,之后,被迫仍然写一个try / catch为相同的条件?
这是一个很好的模式,但检查exception可以由编译器强制执行,这些检查不能。 也不是所有的图书馆都是用这种devise模式制作的,只是在考虑例外时才需要记住。
每一个尝试都需要logging很多信息,例如堆栈指针,CPU寄存器的值等等,这样它可以展开堆栈,并在返回通过try块时的状态,以防引发exception。 不仅每一次尝试都需要logging大量的信息,当抛出一个exception时,需要恢复很多值。 所以一个尝试是非常昂贵的, 投掷/捕获也是非常昂贵的。
这并不意味着你不应该使用exception,但是,在性能严重的代码中,你可能不应该使用太多的尝试,也不会经常抛出exception。