Javaexception有多慢?
问题:Java中的exception处理实际上很慢吗?
传统的观点以及大量的Google结果都表明,特殊的逻辑不应该用于Java中的正常程序stream程。 通常有两个原因,
- 它真的很慢 – 甚至比普通代码慢一个数量级(所给出的原因各不相同),
和
- 它杂乱无章,因为人们只希望在特殊代码中处理错误。
这个问题是关于#1。
作为一个例子, 这个页面将Javaexception处理描述为“非常慢”,并将慢速与创buildexception消息string联系起来 – “这个string被用于创build抛出的exception对象,这不是很快。 Java中的有效exception处理( The Effective Exception Handling)在Java中表示,“其原因是由于exception处理的对象创build方面的原因,从而导致exception抛出exception缓慢”。 另一个原因是堆栈跟踪的生成是减慢速度。
我的testing(在32位Linux上使用Java 1.6.0_07,Java HotSpot 10.0)表明exception处理不会比普通代码慢。 我试着在执行一些代码的循环中运行一个方法。 在该方法的最后,我使用一个布尔值来指示是返回还是抛出 。 这样的实际处理是一样的。 我试着以不同的顺序运行这些方法,并平均testing时间,认为这可能是JVM热身。 在我所有的testing中,投掷的速度至less与回归速度一样快,如果不快的话(速度快3.1%)。 我对我的testing是错误的可能性完全保持开放,但是在代码示例,testing比较或上一两年的结果中,我没有看到任何东西,它们显示Java中的exception处理实际上是慢的。
导致我走这条路的是我需要使用的API,它将exception作为正常控制逻辑的一部分。 我想纠正他们的用法,但现在我可能无法。 相反,我会不得不赞扬他们的前瞻性思维?
在“ 即时编译中的高效的Javaexception处理”一文中 ,作者提出,即使没有抛出任何exception,单独存在exception处理程序也足以防止JIT编译器正确优化代码,从而降低其速度。 我还没有testing过这个理论。
这取决于如何执行例外。 最简单的方法是使用setjmp和longjmp。 这意味着CPU的所有寄存器都被写入堆栈(这已经花费了一些时间),并且可能还需要创build其他一些数据…所有这些都已经在try语句中发生了。 throw语句需要展开堆栈并恢复所有寄存器的值(以及VM中可能的其他值)。 因此,尝试和扔是同样缓慢,这是非常缓慢的,但是如果没有exception抛出,退出try块在大多数情况下(因为一切都放在堆栈,如果方法存在自动清理)没有任何时间。
Sun和其他人认识到,这可能不是最理想的,当然VM在这段时间内会越来越快。 还有另一种方式来实现exception,这使得尝试自己闪电般快(实际上根本没有什么事情发生,一般情况下 – 所有需要发生的事情都是在虚拟机加载类时已经完成了),并且使得抛出速度不尽如人意。 我不知道哪个JVM使用这个新的,更好的技术…
…但你是用Java编写的,所以你以后的代码只能运行在一个特定系统上的一个JVM上? 因为如果它可能运行在任何其他平台或任何其他JVM版本(可能是任何其他供应商)上,谁说他们也使用快速实现? 快速的比慢的更复杂,不容易在所有的系统上使用。 你想保持便携? 那么不要依赖快速的例外。
在一个try块中你也做了很大的改变。 如果你打开一个try块,并且从来没有在这个try块中调用任何方法,那么try块将会是非常快的,因为JIT可以实际上像一个简单的goto一样处理一个throw。 它不需要保存堆栈状态,也不需要在抛出exception时展开堆栈(只需跳到catch处理程序)。 但是,这通常不是你通常所做的。 通常你打开一个try块,然后调用一个可能会抛出exception的方法,对吧? 即使你只是在你的方法中使用try块,这种方法是什么样的,不会调用任何其他方法? 它只是计算一个数字? 那么你需要什么例外? 有更多优雅的方式来规范程序stream程。 除了简单的math之外,还有其他一些其他的东西,你将不得不调用一个外部方法,这已经破坏了本地try块的优点。
请参阅以下testing代码:
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() ); } }
结果:
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放在同一个方法(method3)中,结果不会那么糟糕,但这是我不能依赖的一个特殊的JIT优化。 即使使用这种优化,投掷仍然很慢。 所以我不知道你要在这里做什么,但是使用try / catch / throw肯定有更好的方法。
仅供参考,我扩展了Mecki做的实验:
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
前三个和Mecki一样(我的笔记本电脑显然比较慢)。
method4与method3是相同的,只不过它创build一个new Integer(1)
而不是throw new Exception()
。
method5就像method3,只不过它创build了new Exception()
而不抛出它。
method6就像method3,只不过它抛出了一个预先创build的exception(一个实例variables)而不是创build一个新的exception。
在Java中,引发exception的大部分花费是收集堆栈跟踪所花费的时间,这是在创buildexception对象时发生的。 抛出exception的实际成本虽然很大,但远低于创buildexception的成本。
不幸的是,我的答案太长,不能在这里发布。 所以,让我在这里总结一下,并把你介绍给http://www.fuwjax.com/how-slow-are-java-exceptions/了解细节。;
这里真正的问题不是“与”从未失败的代码“相比,”exception情况报告有多慢“? 正如接受的回应可能让你相信。 相反,问题应该是“与其他方式报告的失败相比,作为例外报告的失败有多慢”? 一般来说,报告失败的另外两种方法是使用标记值或结果封装。
Sentinel值是在成功的情况下返回一个类的尝试,而在失败的情况下则是另一个类的返回。 你可以把它看作是返回一个exception,而不是抛出一个exception。 这需要一个共享的父类与成功的对象,然后做一个“instanceof”检查和一对夫妇获得成功或失败的信息。
事实certificate,在types安全的风险下,哨兵值比例外更快,但只有大约2倍。 现在,这可能看起来很多,但是这个2x仅仅包含了执行差异的成本。 在实践中,这个因子要低得多,因为我们可能失败的方法比一些算术运算符更有趣,如本页其他地方的示例代码。
结果包装另一方面根本不牺牲types安全。 他们将成功和失败信息包装在一个class级中。 所以不是“instanceof”,而是为成功和失败对象提供“isSuccess()”和getters。 但是,结果对象比使用exception慢大约2 倍 。 事实certificate,每次创build一个新的包装对象比抛出一个exception有时更昂贵。
最重要的是,exception是用语言提供的一种方法,表明一种方法可能会失败。 没有其他的方法可以从API中知道哪些方法预计总是(大部分)工作,哪些预计会报告失败。
exception比哨兵更安全,比结果对象更快,而且比任何一个都less。 我不build议try / catchreplaceif / else,但是exception是报告失败的正确方法,即使在业务逻辑中也是如此。
这就是说,我想指出的是,两个最常见的方式,显着影响性能我碰到的是创造不必要的对象和嵌套循环。 如果您有创buildexception或不创buildexception的select,请不要创buildexception。 如果您有时会创buildexception或者始终创build另一个对象,则可以select创buildexception。
AlekseyShipilëv做了一个非常全面的分析 ,他在各种条件组合下testing了Javaexception:
- 新创build的exception与预先创build的exception
- 堆栈跟踪启用vs禁用
- 请求堆栈跟踪,从未请求
- 陷入顶级水平,在每一个级别都被重新摧毁,在每一个级别都被链接/包裹
- 各种级别的Java调用堆栈深度
- 没有内联优化vs极限内联与默认设置
- 用户定义的字段读取与不读取
他还将它们与在不同错误频率级别检查错误代码的性能进行比较。
结论(引自他的post)是:
-
真正例外的例外是美丽的表演。 如果按照devise使用它们,只能在正规代码处理的绝大多数非例外情况中传达真正的例外情况,那么使用例外就是performance胜利。
-
exception的性能成本有两个主要组成部分:exception实例化时的堆栈跟踪结构 ,exception抛出期间的堆栈展开 。
-
堆栈跟踪构build成本与exception实例化时的堆栈深度成正比 。 那已经很糟糕了,因为地球上的人知道这个投掷方法被调用的堆栈深度。 即使您closures堆栈跟踪生成和/或cachingexception,您也只能摆脱这部分性能成本。
-
堆栈展开成本取决于我们在编译代码中使exception处理程序靠得更近的幸运程度。 仔细地构造代码以避免深层的exception处理程序查找可能会帮助我们获得更多的幸运。
-
如果我们消除这两种效应,则例外的性能成本就是本地分支的性能成本。 不pipe这听起来多么美妙,这并不意味着你应该使用exception作为通常的控制stream程,因为在这种情况下, 你是在优化编译器的摆布! 您应该只在真正例外的情况下使用它们,例外频率会分摊提高实际例外的可能的不幸成本。
-
乐观的经验法则似乎是10 ^ -4的频率,例外是足够的例外。 这当然取决于exception本身的重量,exception处理程序中采取的确切行动等。
结果是,当一个exception不被抛出时,你不需要付出代价,所以当exception情况足够less时,exception处理比每次使用一个if
都要快。 全文很值得一读。
我已经扩展了@Mecki和@incarnate给出的答案,没有Java的堆栈填充。
对于Java 7+,我们可以使用Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace)
。 但对于Java6,请参阅我对这个问题的回答
// 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() ); }
使用Java 1.6.0_45在Core i7,8GB RAM上输出:
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
所以,与抛出exception的方法相比,仍然返回值的方法更快。 恕我直言,我们不能devise一个明确的API只是使用返回types的成功和错误stream。 在没有堆栈跟踪的情况下抛出exception的方法比正常exception快4-5倍。
编辑:NoStackTraceThrowable.java 谢谢@Greg
public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
不知道这些话题是否相关,但是我曾经想过依靠当前线程的堆栈跟踪来实现一个技巧:我想发现方法的名字,这触发了实例化类中的实例化(yeap,这个想法是疯狂的,我完全放弃了)。 所以我发现调用Thread.currentThread().getStackTrace()
是非常慢的(由于它使用内部使用本地dumpThreads
方法)。
所以Java Throwable
相应地有一个本地方法fillInStackTrace
。 我认为前面描述的杀手 – catch
块会以某种方式触发这个方法的执行。
但是让我告诉你另一个故事
在Scala中,一些function特性在JVM中使用ControlThrowable
进行编译,该扩展Throwable
并覆盖fillInStackTrace
,方法如下:
override def fillInStackTrace(): Throwable = this
所以我适应了上面的testing(周期数量减less了十,我的机器有点慢:):
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) }
所以,结果是:
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
你看, method3
和method3
唯一的区别是它们抛出了不同的exception。 Yeap方法method4
仍然比方法1和方法2慢,但差异更加可以接受。
我认为第一篇文章是指遍历调用堆栈和创build堆栈跟踪作为昂贵的部分的行为,虽然第二篇文章没有说,但我认为这是对象创build中最昂贵的部分。 约翰·罗斯有一篇文章,他描述了加速exception的不同技术 。 (预先分配和重用exception,没有堆栈跟踪的exception等)
但是,我认为这应该只是一个必要的罪恶,最后的手段。 John这样做的理由是模仿JVM中还没有的其他语言的特性。 你不应该习惯于使用控制stream的exception。 尤其不是出于性能原因! 正如你自己在#2中提到的那样,你可能冒这样的风险来掩盖你的代码中的严重错误,这对于新程序员来说将更难维护。
Java中的Microbenchmarks出乎意料地难以正确(我被告知),特别是当你进入JIT领域的时候,所以我真的怀疑,在现实生活中使用exception比“回归”更快。 例如,我怀疑你的testing中有2到5个栈帧。 现在假设您的代码将被JBoss部署的JSF组件调用。 现在你可能有一个长达几页的堆栈跟踪。
也许你可以发布你的testing代码?
我已经使用JVM 1.5进行了一些性能testing,使用exception的速度至less慢了2倍。 平均来说:一个小方法的执行时间增加了三倍(3倍),但是有例外。 不得不赶上例外的一个微不足道的小圈子增加了两倍的自我时间。
我在生产代码中看到了类似的数字以及微观基准。
例外情况肯定不能用于任何经常被调用的东西。 抛出数以千计的例外,会导致一个巨大的瓶颈。
例如,使用“Integer.ParseInt(…)”在非常大的文本文件中查找所有不良值 – 非常糟糕的想法。 (我已经看到这种实用方法杀死生产代码的性能)
使用exception来报告用户GUI表单上的错误值,从性能的angular度来看可能并不那么糟糕。
无论它是否是一个好的devise实践,我会遵循规则:如果错误是正常的/预期的,那么使用返回值。 如果不正常,请使用例外。 例如:读取用户input,错误的值是正常的 – 使用错误代码。 将值传递给内部效用函数,应该通过调用代码来过滤错误的值 – 使用exception。
后来我写了一个类来testing使用两种方法将string转换为整数的相对性能:(1)调用Integer.parseInt()并捕获exception;(2)将string与正则expression式匹配,并调用parseInt()只有比赛成功。 我以最有效的方式使用正则expression式(即在插入循环之前创build模式和匹配对象),并且不打印或保存exception中的堆栈轨迹。
对于一万个string的列表,如果它们都是有效的数字,那么parseInt()方法是正则expression式方法的四倍。 但是,如果只有80%的string是有效的,则正则expression式的速度是parseInt()的两倍。 而如果20%是有效的,意味着exception被抛出并抓取了80%的时间,那么正则expression式大约是parseInt()的二十倍。
我对结果感到惊讶,考虑到正则expression式方法处理有效的string两次:一次为匹配,再次为parseInt()。 但是抛出和捕捉例外不止于此。 这种情况在现实世界中不可能经常发生,但如果是这样的话,那么绝对不应该使用exception捕捉技术。 但是,如果你只是validation用户input或类似的东西,通过一切手段使用parseInt()方法。
即使抛出一个exception并不慢,但为正常的程序stream抛出exception仍然是一个坏主意。 用这种方式它类似于一个GOTO …
我想这并不能真正回答这个问题。 我可以想象,在早期的java版本(<1.4)中抛出exception缓慢的'传统'智慧是真实的。 创build一个exception需要VM创build整个堆栈跟踪。 从那时起,虚拟机上的许多变化加快了速度,这可能是一个改进的领域。
Java和C#中的exception性能还有很多不足之处。
作为程序员,这迫使我们按照“很less引发exception”的规则来生活,只是出于实际的性能原因。
但是,作为计算机科学家,我们应该反抗这个有问题的状态。 编写函数的人往往不知道多久会被调用,或者成功或失败的可能性更大。 只有来电者有这个信息。 试图避免exception会导致不清楚的API idoms,在某些情况下,我们只有干净但缓慢的exception版本,在其他情况下,我们有快速但笨重的返回值错误,在其他情况下,我们最终都会。 库实现者可能必须编写和维护两个版本的API,调用者必须决定在每种情况下使用哪个版本。
这是一个混乱。 如果例外情况有更好的performance,我们可以避免这些笨拙的成语,并使用例外,因为它们被用来作为一个结构化的错误返回工具。
我真的很希望看到使用更接近于返回值的技术来实现exception机制,所以我们可以使性能更接近于返回值。因为这是我们在性能敏感的代码中返回的。
这里是一个将exception性能与错误返回值性能进行比较的代码示例。
公共类TestIt {
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\n", 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\n", 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\n", recurse_depth, exception_freq, failures,elapsed_time); } } }
}
And here are the results:
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
Checking and propagating return-values does add some cost vs the baseline-null call, and that cost is proportional to call-depth. At a call-chain depth of 8, the error-return-value checking version was about 27% slower than the basline version which did not check return values.
Exception performance, in comparison, is not a function of call-depth, but of exception frequency. However, the degredation as exception frequency increases is much more dramatic. At only a 25% error frequency, the code ran 24-TIMES slower. At an error frequency of 100%, the exception version is almost 100-TIMES slower.
This suggests to me that perhaps are making the wrong tradeoffs in our exception implementations. Exceptions could be faster, either by avoiding costly stalk-walks, or by outright turning them into compiler supported return-value checking. Until they do, we're stuck avoiding them when we want our code to run fast.
HotSpot is quite capable of removing exception code for system generated exceptions, so long as it is all inlined. However, explicitly created exception and those otherwise not removed spend a lot of time creating the stack trace. Override fillInStackTrace
to see how this can affect performance.
Just compare let's say Integer.parseInt to the following method, which just returns a default value in the case of unparseable data instead of throwing an Exception:
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; }
As long as you apply both methods to "valid" data, they both will work at approximately the same rate (even although Integer.parseInt manages to handle more complex data). But as soon as you try to parse invalid data (eg to parse "abc" 1.000.000 times), the difference in performance should be essential.
I changed @Mecki 's answer above to have method1 return a boolean and a check in the calling method, as you cannot just replace an Exception with nothing. After two runs, method1 was still either the fastest or as fast as method2.
Here is snapshot of the code:
// 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!"); } }
and results:
Run 1
method1 took 841 ms, result was 2 method2 took 841 ms, result was 2 method3 took 85058 ms, result was 2
运行2
method1 took 821 ms, result was 2 method2 took 838 ms, result was 2 method3 took 85929 ms, result was 2
My opinion about Exception speed versus checking data programmatically.
Many classes had String to value converter (scanner / parser), respected and well-known libraries too 😉
usually has form
class Example { public static Example Parse(String input) throws AnyRuntimeParsigException ... }
exception name is only example, usually is unchecked (runtime), so throws declaration is only my picture
sometimes exist second form:
public static Example Parse(String input, Example defaultValue)
never throwing
When the second ins't available (or programmer read too less docs and use only first), write such code with regular expression. Regular expression are cool, politically correct etc:
Xxxxx.regex(".....pattern", src); if(ImTotallySure) { Example v = Example.Parse(src); }
with this code programmers hasn't cost of exceptions. BUT HAS comparable very HIGH cost of regular expressions ALWAYS versus small cost of exception sometimes.
I use almost always in such context
try { parse } catch(ParsingException ) // concrete exception from javadoc { }
without analysing stacktrace etc, I believe after lectures of Yours quite speed.
Do not be afraid Exceptions
Why should exceptions be any slower than normal returns?
As long as you don't print the stacktrace to the terminal, save it into a file or something similar, the catch-block doesn't do any more work than other code-blocks. So, I can't imagine why "throw new my_cool_error()" should be that slow.
Good question and I'm looking forward to further information on this topic!