How slow are Java exceptions?
问题:Java中的异常处理实际上是慢的吗?
传统的智慧以及大量的谷歌结果表明,在爪哇正常的程序流程不应该使用例外逻辑。通常有两个原因:
和
这个问题是关于1的。
例如,此页将Java异常处理描述为"非常慢",并将慢度与异常消息字符串的创建联系起来——"该字符串然后用于创建抛出的异常对象。这是不太快的。"Java中的有效的异常处理称,"这是由于异常处理的对象创建方面,这使得投掷异常本质上是缓慢的"。另一个原因是堆栈跟踪的生成会减慢它的速度。
我的测试(使用Java1.60Y07,Java热点10,在32位Linux上)表明异常处理并不比常规代码慢。我尝试在执行一些代码的循环中运行一个方法。在方法的末尾,我使用一个布尔值来指示是返回还是抛出。这样,实际处理是相同的。我尝试以不同的顺序运行这些方法,并平均我的测试时间,认为这可能是JVM预热。在我所有的测试中,投掷速度至少和返回速度一样快,如果不是更快的话(高达3.1%)。我完全同意我的测试是错误的,但我在过去的一两年中没有看到代码示例、测试比较或结果的任何方式,这表明Java中的异常处理实际上是缓慢的。
引导我走上这条路的是一个API,我需要使用它将异常作为正常控制逻辑的一部分抛出。我想纠正他们的用法,但现在我可能无法纠正。相反,我会称赞他们的前瞻性思维吗?
在即时编译中的高效Java异常处理中,作者建议单独存在异常处理程序,即使没有抛出异常,也足以防止JIT编译器对代码进行适当的优化,从而减慢代码的速度。我还没有检验过这个理论。
这取决于如何实现异常。最简单的方法是使用setjmp和longjmp。这意味着CPU的所有寄存器都被写入堆栈(这已经需要一些时间),可能还需要创建一些其他数据…所有这些都已在try语句中发生。throw语句需要展开堆栈并恢复所有寄存器的值(以及虚拟机中可能的其他值)。因此,try和throw同样慢,这相当慢,但是如果没有抛出异常,退出try块在大多数情况下不会花费任何时间(因为所有东西都放在堆栈上,如果方法存在,堆栈会自动清理)。
Sun和其他人认识到,这可能是次优的,当然,随着时间的推移,虚拟机越来越快。还有另一种实现异常的方法,它使Try本身变得非常快(实际上,Try根本没有发生任何事情——当类由VM加载时,需要发生的所有事情都已经完成了),并且它使得抛出速度没有那么慢。我不知道哪个JVM使用了这种新的、更好的技术…
……但你是用Java编写的,所以以后的代码只在一个特定系统上运行在一个JVM上吗?既然它可能在任何其他平台或任何其他JVM版本上运行(可能是任何其他供应商的版本),谁说他们也使用快速实现?快的系统比慢的系统复杂,在所有系统中都不容易实现。你想随身携带吗?那就不要依赖异常的快速性。
你在试块中做的事情也会有很大的不同。如果您打开一个try块,并且从未在此try块中调用任何方法,那么try块将是非常快的,因为JIT可以像处理一个简单的goto一样处理一个throw。它既不需要保存堆栈状态,也不需要在引发异常时展开堆栈(它只需要跳转到catch处理程序)。然而,这不是你通常做的。通常打开一个try块,然后调用一个可能引发异常的方法,对吗?即使您只是在方法中使用try块,这将是什么类型的方法,不调用任何其他方法?它能计算出一个数字吗?那你需要例外吗?有很多更优雅的方法来调节程序流。对于除了简单的数学以外的其他几乎所有东西,您必须调用一个外部方法,这已经破坏了本地try块的优势。
请参见以下测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | public class Test { int value; public int getValue() { return value; } public void reset() { value = 0; } // Calculates without exception public void method1(int i) { value = ((value + i) / i) << 1; // Will never be true if ((i & 0xFFFFFFF) == 1000000000) { System.out.println("You'll never see this!"); } } // Could in theory throw one, but never will public void method2(int i) throws Exception { value = ((value + i) / i) << 1; // Will never be true if ((i & 0xFFFFFFF) == 1000000000) { throw new Exception(); } } // This one will regularly throw one public void method3(int i) throws Exception { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new Exception(); } } public static void main(String[] args) { int i; long l; Test t = new Test(); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { t.method1(i); } l = System.currentTimeMillis() - l; System.out.println( "method1 took" + l +" ms, result was" + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method2(i); } catch (Exception e) { System.out.println("You'll never see this!"); } } l = System.currentTimeMillis() - l; System.out.println( "method2 took" + l +" ms, result was" + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method3(i); } catch (Exception e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println( "method3 took" + l +" ms, result was" + t.getValue() ); } } |
结果:
1 2 3 | method1 took 972 ms, result was 2 method2 took 1003 ms, result was 2 method3 took 66716 ms, result was 2 |
来自try块的减速太小,无法排除诸如后台进程之类的混杂因素。但是挡块杀死了所有的东西,使它慢了66倍!
如我所说,如果将try/catch和throw都放在同一方法(method3)中,结果不会那么糟糕,但这是一个我不会依赖的特殊的JIT优化。即使使用这种优化,抛出速度仍然相当慢。所以我不知道你想在这里做什么,但肯定有比使用try/catch/throw更好的方法。
仅供参考,我扩展了Mecki做的实验:
1 2 3 4 5 6 | method1 took 1733 ms, result was 2 method2 took 1248 ms, result was 2 method3 took 83997 ms, result was 2 method4 took 1692 ms, result was 2 method5 took 60946 ms, result was 2 method6 took 25746 ms, result was 2 |
前3个和Mecki的一样(我的笔记本明显慢了)。
方法4与方法3相同,只是它创建了一个
方法5与方法3类似,只是它创建了
方法6与方法3类似,只是它抛出一个预先创建的异常(实例变量),而不是创建一个新的异常。
在Java中,抛出异常的大部分开销是收集堆栈跟踪所花费的时间,这是在创建异常对象时发生的。抛出异常的实际成本虽然很大,但却大大低于创建异常的成本。
Aleksey Shipil?V做了一个非常彻底的分析,他在各种条件组合下对Java异常进行了基准测试:
- 新创建的异常与预创建的异常
- 堆栈跟踪已启用vs已禁用
- 请求的堆栈跟踪与从未请求的堆栈跟踪
- 在顶层捕获,在每一层回收,在每一层链接/包装
- Java调用堆栈深度的不同级别
- 没有内联优化与极端内联与默认设置
- 用户定义字段已读与未读
他还将其与在不同级别的错误频率下检查错误代码的性能进行了比较。
结论(从他的职位逐字引用)是:
真正的例外是出色的表现。如果您按照设计使用它们,并且只在由常规代码处理的绝大多数非异常情况中传达真正的异常情况,那么使用异常就是性能的胜利。
异常的性能开销有两个主要组成部分:异常实例化时的堆栈跟踪构造和异常引发期间的堆栈展开。
堆栈跟踪构建成本与异常实例化时的堆栈深度成正比。这已经很糟糕了,因为到底谁知道调用这个抛出方法的堆栈深度呢?即使关闭堆栈跟踪生成和/或缓存异常,也只能除去这部分性能成本。
堆栈展开成本取决于我们在编译代码中使异常处理程序更接近的幸运程度。仔细构造代码以避免深度异常处理程序查找可能有助于我们更幸运。
如果我们消除这两种影响,异常的性能成本就是本地分支的性能成本。不管它听起来多么美妙,这并不意味着您应该将异常作为通常的控制流使用,因为在这种情况下,您将受制于优化编译器!您应该只在真正异常的情况下使用它们,在这种情况下,异常频率会分摊引发实际异常的可能的不幸成本。
乐观的经验法则似乎是10^-4的频率,因为例外情况是足够特殊的。当然,这取决于异常本身的权重、异常处理程序中所采取的确切操作等。
结果是,如果不抛出异常,就不会付出代价,因此,当异常情况足够罕见时,异常处理比每次使用
不幸的是,我的答案太长了,无法在这里发布。因此,让我在这里总结一下,并参考http://www.fuwjax.com/how-slow-are-java-exceptions/了解详细信息。
这里真正的问题不是"将失败报告为异常"与"从不失败的代码"相比有多慢?"正如你所相信的那样。相反,问题应该是"与以其他方式报告的故障相比,将‘故障报告为异常’的速度有多慢?"通常,报告失败的另外两种方法要么是使用sentinel值,要么是使用结果包装器。
sentinel值是在成功的情况下尝试返回一个类,在失败的情况下尝试返回另一个类。你可以把它看作是返回一个异常而不是抛出一个异常。这需要一个与success对象共享的父类,然后执行"instanceof"检查和一些强制转换以获取成功或失败信息。
事实证明,在类型安全的风险下,sentinel值比异常值快,但只有大约2倍的系数。现在,这看起来可能很多,但2倍只涵盖了实现差异的成本。在实践中,这个因素要低得多,因为我们可能失败的方法比本页其他地方的示例代码中的一些算术运算符更有趣。
另一方面,结果包装器根本不牺牲类型安全。它们将成功和失败信息包装在一个类中。因此,它们不提供"instanceof",而是为成功和失败对象提供"issueaccess()"和getter。但是,结果对象比使用异常慢大约2倍。事实证明,每次创建一个新的包装器对象都比有时抛出异常要昂贵得多。
除此之外,异常是语言提供的指示方法可能失败的方式。除了API,没有其他方法可以分辨出哪些方法总是(大部分)有效,哪些方法会报告失败。
异常比哨兵更安全,比结果对象更快,比两者都不令人惊讶。我并不建议尝试/捕获替换if/else,但异常是报告失败的正确方法,即使在业务逻辑中也是如此。
也就是说,我要指出的是,对性能产生重大影响的两种最常见的方法是创建不必要的对象和嵌套循环。如果在创建异常或不创建异常之间有选择,请不要创建异常。如果您可以在有时创建异常或始终创建另一个对象之间进行选择,则创建异常。
我已经扩展了由@ MeCKI和"化身"给出的答案,而没有StasTr踪迹填充Java。
使用Java 7 +,我们可以使用EDCOX1 0。但是对于Java6,请看我对这个问题的回答。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // This one will regularly throw one public void method4(int i) throws NoStackTraceThrowable { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new NoStackTraceThrowable(); } } // This one will regularly throw one public void method5(int i) throws NoStackTraceRuntimeException { value = ((value + i) / i) << 1; // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both // an AND operation between two integers. The size of the number plays // no role. AND on 32 BIT always ANDs all 32 bits if ((i & 0x1) == 1) { throw new NoStackTraceRuntimeException(); } } public static void main(String[] args) { int i; long l; Test t = new Test(); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method4(i); } catch (NoStackTraceThrowable e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println("method4 took" + l +" ms, result was" + t.getValue() ); l = System.currentTimeMillis(); t.reset(); for (i = 1; i < 100000000; i++) { try { t.method5(i); } catch (RuntimeException e) { // Do nothing here, as we will get here } } l = System.currentTimeMillis() - l; System.out.println("method5 took" + l +" ms, result was" + t.getValue() ); } |
用Java1.60y45输出,在核心i7,8GB RAM上:
1 2 3 4 5 | method1 took 883 ms, result was 2 method2 took 882 ms, result was 2 method3 took 32270 ms, result was 2 // throws Exception method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException |
因此,与抛出异常的方法相比,返回值的方法仍然更快。imho,我们不能只为成功和错误流使用返回类型来设计清晰的API。在不使用StackTrace的情况下引发异常的方法比普通异常快4-5倍。
编辑:nostacktracethrowable.java谢谢@greg
1 2 3 4 5 | public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } } |
我认为第一篇文章将遍历调用堆栈并创建堆栈跟踪的行为称为昂贵的部分,而第二篇文章没有这样说,我认为这是创建对象最昂贵的部分。约翰·罗斯在一篇文章中描述了加速例外的不同技术。(预分配和重用异常、没有堆栈跟踪的异常等)
但我仍然认为这只是一种必要的邪恶,最后的手段。约翰这样做的原因是模仿其他语言的特性,而这些语言在JVM中还不可用。您不应该养成使用异常来控制流的习惯。尤其是出于性能原因!正如您在2中所提到的,您可能会以这种方式掩盖代码中的严重错误,而且对于新的程序员来说,维护起来会比较困难。
Java中的微基准很难得到正确的消息(我已经被告知),尤其是当你进入JIT领域时,我真的怀疑使用异常比实际生活中的"返回"要快。例如,我怀疑您的测试中有2到5个堆栈帧?现在假设您的代码将由jboss部署的JSF组件调用。现在您可能有一个长达几页的堆栈跟踪。
也许你可以发布你的测试代码?
不知道这些主题是否相关,但我曾经想依靠当前线程的堆栈跟踪实现一个技巧:我想发现方法的名称,它触发了实例化类内的实例化(是的,这个想法很疯狂,我完全放弃了它)。所以我发现调用
因此,Java EDCOX1〔2〕相应地有一个原生方法EDCOX1×3。我认为前面描述的killer-
但我再告诉你一个故事…
在scala中,一些功能特性是在jvm中使用
1 |
所以我调整了上面的测试(循环量减少了10,我的机器慢了一点:):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | class ControlException extends ControlThrowable class T { var value = 0 def reset = { value = 0 } def method1(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0xfffffff) == 1000000000) { println("You'll never see this!") } } def method2(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0xfffffff) == 1000000000) { throw new Exception() } } def method3(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0x1) == 1) { throw new Exception() } } def method4(i: Int) = { value = ((value + i) / i) << 1 if ((i & 0x1) == 1) { throw new ControlException() } } } class Main { var l = System.currentTimeMillis val t = new T for (i <- 1 to 10000000) t.method1(i) l = System.currentTimeMillis - l println("method1 took" + l +" ms, result was" + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method2(i) } catch { case _ => println("You'll never see this") } l = System.currentTimeMillis - l println("method2 took" + l +" ms, result was" + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method4(i) } catch { case _ => // do nothing } l = System.currentTimeMillis - l println("method4 took" + l +" ms, result was" + t.value) t.reset l = System.currentTimeMillis for (i <- 1 to 10000000) try { t.method3(i) } catch { case _ => // do nothing } l = System.currentTimeMillis - l println("method3 took" + l +" ms, result was" + t.value) } |
因此,结果是:
1 2 3 4 | method1 took 146 ms, result was 2 method2 took 159 ms, result was 2 method4 took 1551 ms, result was 2 method3 took 42492 ms, result was 2 |
你看,
不久前,我编写了一个类来测试使用两种方法将字符串转换为整数的相对性能:(1)调用integer.parseint()并捕获异常;(2)仅当匹配成功时才使用regex匹配字符串并调用parseint()。我以最有效的方式使用了regex(即,在插入循环之前创建模式和匹配器对象),并且没有从异常中打印或保存stacktrace。
对于一个包含10000个字符串的列表,如果它们都是有效数字,则parseInt()方法的速度是regex方法的四倍。但是如果只有80%的字符串是有效的,那么regex的速度是parseInt()的两倍。如果20%是有效的,这意味着异常被抛出并捕获了80%,那么regex的速度大约是parseInt()的20倍。
考虑到regex方法两次处理有效的字符串:一次用于匹配,另一次用于parseInt(),我对此结果感到惊讶。但抛出和捕获异常并不能弥补这一点。这种情况在现实世界中不太可能经常发生,但如果发生,则绝对不应该使用异常捕获技术。但是,如果您只是验证用户输入或类似的内容,那么一定要使用parseInt()方法。
我用JVM1.5做了一些性能测试,使用异常的速度至少慢了2倍。平均:一个非常小的方法的执行时间超过三倍(3x),但有例外。一个必须捕获异常的非常小的循环在自我时间上增加了2倍。
我在生产代码和微基准中看到过类似的数字。
异常绝对不能用于任何频繁调用的内容。一秒钟抛出数千个例外会造成巨大的瓶颈。
例如,使用"integer.parseint(…)"在一个非常大的文本文件中查找所有错误的值——这是一个非常糟糕的主意。(我已经看到这个实用方法在生产代码上杀死了性能)
使用异常在用户GUI窗体上报告一个错误的值,从性能的角度来看,可能还不错。
不管它是否是一个好的设计实践,我都会遵循以下规则:如果错误是正常的/预期的,那么使用一个返回值。如果不正常,使用异常。例如:读取用户输入,错误值是正常的——使用错误代码。将值传递给内部实用程序函数时,应通过调用代码过滤错误值——使用异常。
即使抛出异常不是很慢,为正常的程序流抛出异常仍然是一个坏主意。用这种方式,它类似于goto…
不过,我想这并不能真正回答这个问题。我想,在较早的Java版本(<1.4)中,抛出异常的"常规"智慧是缓慢的。创建异常需要VM创建整个堆栈跟踪。从那时起,在虚拟机中,为了加快速度发生了很大的变化,这可能是一个已经得到改进的领域。
Java和C语言中的异常性能还有很多不足之处。
作为程序员,这迫使我们遵守"例外情况不应经常发生"的规则,仅仅是出于实际的性能原因。
然而,作为计算机科学家,我们应该反抗这种有问题的状态。编写函数的人通常不知道调用函数的频率,也不知道是否更可能成功或失败。只有呼叫者有此信息。试图避免异常会导致不清楚的API IDOM,在某些情况下,我们只有干净但缓慢的异常版本,而在其他情况下,我们有快速但笨拙的返回值错误,在其他情况下,我们最终会出现这两种错误。库实现者可能必须编写和维护两个版本的API,调用方必须决定在每种情况下使用两个版本中的哪一个。
这有点乱。如果异常有更好的性能,我们可以避免使用这些笨拙的习惯用法,并使用异常,因为它们是用来使用的…作为结构化错误返回工具。
我真的很想看到使用更接近返回值的技术实现的异常机制,这样我们可以让性能更接近返回值。因为这是我们在性能敏感的代码中要恢复的。
下面是一个将异常性能与错误返回值性能进行比较的代码示例。
公共类测试{
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | int value; public int getValue() { return value; } public void reset() { value = 0; } public boolean baseline_null(boolean shouldfail, int recurse_depth) { if (recurse_depth <= 0) { return shouldfail; } else { return baseline_null(shouldfail,recurse_depth-1); } } public boolean retval_error(boolean shouldfail, int recurse_depth) { if (recurse_depth <= 0) { if (shouldfail) { return false; } else { return true; } } else { boolean nested_error = retval_error(shouldfail,recurse_depth-1); if (nested_error) { return true; } else { return false; } } } public void exception_error(boolean shouldfail, int recurse_depth) throws Exception { if (recurse_depth <= 0) { if (shouldfail) { throw new Exception(); } } else { exception_error(shouldfail,recurse_depth-1); } } public static void main(String[] args) { int i; long l; TestIt t = new TestIt(); int failures; int ITERATION_COUNT = 100000000; // (0) baseline null workload for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; t.baseline_null(shoulderror,recurse_depth); } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms ", recurse_depth, exception_freq, failures,elapsed_time); } } // (1) retval_error for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; if (!t.retval_error(shoulderror,recurse_depth)) { failures++; } } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms ", recurse_depth, exception_freq, failures,elapsed_time); } } // (2) exception_error for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) { for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) { int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq); failures = 0; long start_time = System.currentTimeMillis(); t.reset(); for (i = 1; i < ITERATION_COUNT; i++) { boolean shoulderror = (i % EXCEPTION_MOD) == 0; try { t.exception_error(shoulderror,recurse_depth); } catch (Exception e) { failures++; } } long elapsed_time = System.currentTimeMillis() - start_time; System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms ", recurse_depth, exception_freq, failures,elapsed_time); } } } |
}
结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121 ms retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141 ms retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334 ms retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487 ms exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763 ms exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367 ms exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775 ms exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116 ms exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms |
检查和传播返回值确实会增加一些成本与基线空调用,并且该成本与调用深度成正比。在调用链深度为8时,错误返回值检查版本比未检查返回值的basline版本慢27%。
相比之下,异常性能不是调用深度的函数,而是异常频率的函数。然而,随着异常频率的增加,衰减更加剧烈。在只有25%的错误频率下,代码运行速度慢了24倍。在100%的错误频率下,异常版本几乎慢100倍。
这对我来说意味着在我们的异常实现中可能做出了错误的权衡。异常可能更快,要么避免代价高昂的跟踪,要么直接将它们转换为编译器支持的返回值检查。在他们这样做之前,当我们希望代码快速运行时,我们一直在回避他们。
只要Hotspot都是内联的,它就能够删除系统生成的异常的异常代码。但是,显式地创建了异常,而那些未删除的异常则花费大量时间来创建堆栈跟踪。覆盖
只需将integer.parsing与以下方法进行比较,后者仅在不可分析数据的情况下返回默认值,而不是引发异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public static int parseUnsignedInt(String s, int defaultValue) { final int strLength = s.length(); if (strLength == 0) return defaultValue; int value = 0; for (int i=strLength-1; i>=0; i--) { int c = s.charAt(i); if (c > 47 && c < 58) { c -= 48; for (int j=strLength-i; j!=1; j--) c *= 10; value += c; } else { return defaultValue; } } return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value; } |
只要将这两种方法应用于"有效"数据,它们都将以大致相同的速率工作(即使integer.parseint能够处理更复杂的数据)。但是,一旦您尝试解析无效数据(例如,解析"abc"1000.000次),性能上的差异就非常重要。
关于异常性能的好文章是:
https://shipilev.net/blog/2014/excellant-performance/
实例化和重用现有的、具有堆栈跟踪和不具有堆栈跟踪等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Benchmark Mode Samples Mean Mean error Units dynamicException avgt 25 1901.196 14.572 ns/op dynamicException_NoStack avgt 25 67.029 0.212 ns/op dynamicException_NoStack_UsedData avgt 25 68.952 0.441 ns/op dynamicException_NoStack_UsedStack avgt 25 137.329 1.039 ns/op dynamicException_UsedData avgt 25 1900.770 9.359 ns/op dynamicException_UsedStack avgt 25 20033.658 118.600 ns/op plain avgt 25 1.259 0.002 ns/op staticException avgt 25 1.510 0.001 ns/op staticException_NoStack avgt 25 1.514 0.003 ns/op staticException_NoStack_UsedData avgt 25 4.185 0.015 ns/op staticException_NoStack_UsedStack avgt 25 19.110 0.051 ns/op staticException_UsedData avgt 25 4.159 0.007 ns/op staticException_UsedStack avgt 25 25.144 0.186 ns/op |
取决于堆栈跟踪的深度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Benchmark Mode Samples Mean Mean error Units exception_0000 avgt 25 1959.068 30.783 ns/op exception_0001 avgt 25 1945.958 12.104 ns/op exception_0002 avgt 25 2063.575 47.708 ns/op exception_0004 avgt 25 2211.882 29.417 ns/op exception_0008 avgt 25 2472.729 57.336 ns/op exception_0016 avgt 25 2950.847 29.863 ns/op exception_0032 avgt 25 4416.548 50.340 ns/op exception_0064 avgt 25 6845.140 40.114 ns/op exception_0128 avgt 25 11774.758 54.299 ns/op exception_0256 avgt 25 21617.526 101.379 ns/op exception_0512 avgt 25 42780.434 144.594 ns/op exception_1024 avgt 25 82839.358 291.434 ns/op |
有关其他详细信息(包括来自JIT的X64汇编程序),请阅读原始博客文章。
这意味着Hibernate/Spring/etc EE大便会由于异常(xd)和重写应用程序控制流远离异常(用
我将上面的@mecki的答案更改为让method1返回一个布尔值并签入调用方法,因为您不能用什么都不替换异常。两次运行后,method1仍然是最快的,或者和method2一样快。
以下是代码的快照:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Calculates without exception public boolean method1(int i) { value = ((value + i) / i) << 1; // Will never be true return ((i & 0xFFFFFFF) == 1000000000); } .... for (i = 1; i < 100000000; i++) { if (t.method1(i)) { System.out.println("Will never be true!"); } } |
结果:
跑1
1 2 3 | method1 took 841 ms, result was 2 method2 took 841 ms, result was 2 method3 took 85058 ms, result was 2 |
跑2
1 2 3 | method1 took 821 ms, result was 2 method2 took 838 ms, result was 2 method3 took 85929 ms, result was 2 |
我对异常速度和以编程方式检查数据的看法。
许多类都有字符串到值转换器(scanner/parser),也有受人尊敬的知名库;)
通常有形式
1 2 3 4 |
异常名称是唯一的示例,通常不选中(运行时),因此throws声明只是我的图片
有时存在第二种形式:
1 |
从不投掷
当第二个INS不可用时(或者程序员读取的文档太少,只使用第一个),用正则表达式编写这样的代码。正常表达是冷静的、政治正确的等:
1 2 3 4 5 | Xxxxx.regex(".....pattern", src); if(ImTotallySure) { Example v = Example.Parse(src); } |
有了这段代码,程序员就不用为异常付出代价了。但有时正则表达式的成本非常高,而异常的成本却很小。
我几乎总是在这种情况下使用
1 2 3 | try { parse } catch(ParsingException ) // concrete exception from javadoc { } |
在没有分析StackTrace等的情况下,我相信在你讲完演讲后,你的速度相当快。
不要害怕例外
为什么异常会比正常返回慢?
只要不将stacktrace打印到终端,将其保存到文件或类似的文件中,catch块就不会比其他代码块做更多的工作。所以,我无法想象为什么"throw new my_cool_error()"会这么慢。
好问题,我期待关于这个主题的更多信息!