|
|
1
360
这取决于如何实现例外。最简单的方法是使用setjmp和longjmp。这意味着CPU的所有寄存器都被写入堆栈(这已经需要一些时间),可能还需要创建一些其他数据。..所有这些都已经在try语句中发生了。throw语句需要展开堆栈并恢复所有寄存器的值(以及VM中可能的其他值)。所以try和throw同样慢,这相当慢,但是如果没有抛出异常,在大多数情况下退出try块不需要任何时间(因为所有东西都放在堆栈上,如果方法存在,堆栈会自动清理)。 Sun和其他人认识到,这可能是次优的,当然虚拟机会随着时间的推移变得越来越快。还有另一种实现异常的方法,它使try本身非常快(实际上try根本不会发生任何事情——所有需要发生的事情都已经在VM加载类时完成了),并且它使throw不那么慢。我不知道哪个JVM使用了这种新的、更好的技术。.. …但你是用Java编写的,所以你的代码以后只能在一个特定系统上的一个JVM上运行吗?既然它可能在任何其他平台或任何其他JVM版本(可能是任何其他供应商的版本)上运行,谁说他们也使用快速实现?快速的比慢速的更复杂,而且不容易在所有系统上实现。你想随身携带吗?那么,不要指望异常很快。 在try区块内做什么也会产生很大的不同。如果你打开一个try块,并且从不从该try块中调用任何方法,那么try块将非常快,因为JIT实际上可以将throw视为简单的goto。它既不需要保存堆栈状态,也不需要在抛出异常时解除堆栈(它只需要跳转到catch处理程序)。然而,这不是你通常会做的。通常你打开一个try块,然后调用一个可能会抛出异常的方法,对吧?即使你只是在方法中使用try块,这是一种什么样的方法,不调用任何其他方法?它只会计算一个数字吗?那么,你为什么需要例外呢?有很多更优雅的方法来调节程序流。除了简单的数学之外,几乎任何其他事情都必须调用外部方法,这已经破坏了本地try块的优势。 请参阅以下测试代码:
结果:
试块的减速太小,无法排除背景进程等混杂因素。但接球块杀死了一切,使速度慢了66倍! 正如我所说的,如果你把try/catch和throw都放在同一个方法(method3)中,结果不会那么糟糕,但这是一种特殊的JIT优化,我不会依赖它。即使使用这种优化,投掷仍然很慢。所以我不知道你在这里想做什么,但肯定有比使用try/catch/stw更好的方法。 |
|
|
2
273
仅供参考,我扩展了Mecki所做的实验:
前3个与Mecki的相同(我的笔记本电脑明显较慢)。
方法4与方法3相同,除了它创建了一个
method5与method3类似,除了它创建了
method6与method3类似,只是它抛出了一个预先创建的异常(一个实例变量),而不是创建一个新的异常。 在Java中,抛出异常的大部分费用是收集堆栈跟踪所花费的时间,这发生在创建异常对象时。抛出异常的实际成本虽然很大,但远低于创建异常的成本。 |
|
|
3
80
Aleksey Shipilv做了一个 very thorough analysis 其中,他在各种条件组合下对Java异常进行了基准测试:
他还将它们与在不同错误频率下检查错误代码的性能进行了比较。 结论(逐字引用自他的帖子)是:
结果是,当没有抛出异常时,您不需要支付成本,因此当异常情况足够罕见时,异常处理比使用
|
|
|
4
41
不幸的是,我的答案太长了,无法在这里发布。所以,让我在这里总结一下,并推荐你参考 http://www.fuwjax.com/how-slow-are-java-exceptions/ 对于那些粗糙的细节。 这里真正的问题不是“与‘从不失败的代码’相比,‘作为异常报告的失败’有多慢?”正如公认的答案可能会让你相信的那样。相反,问题应该是“与其他方式报告的失败相比,‘作为异常报告的失败’有多慢?”一般来说,报告失败的其他两种方式要么是使用哨兵值,要么是使用结果包装器。 Sentinel值是在成功的情况下尝试返回一个类,在失败的情况下返回另一个类。你可以把它看作是返回一个异常而不是抛出一个异常。这需要一个与success对象共享的父类,然后进行“instanceof”检查和几次强制转换以获取成功或失败信息。 事实证明,在类型安全的风险下,Sentinel值比异常快,但只有大约2倍。现在,这可能看起来很多,但2x只涵盖了实现差异的成本。在实践中,这个系数要低得多,因为我们可能失败的方法比本页其他地方的示例代码中的一些算术运算符有趣得多。 另一方面,结果包装器根本不会牺牲类型安全性。他们将成功和失败的信息打包在一个类中。因此,它们为成功和失败对象提供了一个“isSuccess()”和getter,而不是“instanceof”。然而,结果对象大约是2x 缓慢地 而不是使用例外。事实证明,每次创建一个新的包装器对象比有时抛出异常要昂贵得多。 除此之外,异常是语言提供的指示方法可能失败的方式。除了API,没有其他方法可以判断哪些方法应该始终(主要)工作,哪些方法应该报告失败。 异常比哨兵更安全,比结果对象更快,也比两者都不那么令人惊讶。我并不是建议try/catch替换if/else,但异常是报告失败的正确方式,即使在业务逻辑中也是如此。 也就是说,我想指出,我遇到的两种最常见的严重影响性能的方法是创建不必要的对象和嵌套循环。如果您可以在创建异常或不创建异常之间进行选择,请不要创建异常。如果您可以在有时创建异常或始终创建另一个对象之间做出选择,那么请创建异常。 |
|
|
5
21
我扩展了以下给出的答案 @Mecki 和 @incarnate ,Java没有堆栈跟踪填充。
使用Java 7+,我们可以
使用Java 1.6.0_45在Core i7上输出,8GB RAM:
因此,与抛出异常的方法相比,返回值的方法仍然更快。IMHO,我们不能设计一个清晰的API,只使用成功和成功的返回类型;错误流。在没有堆栈跟踪的情况下抛出异常的方法比普通异常快4-5倍。 编辑:NoStackTraceThrowable.java 谢谢@Greg
|
|
|
6
8
不久前,我编写了一个类来测试使用两种方法将字符串转换为整数的相对性能:(1)调用Integer.parseInt()并捕获异常,或(2)将字符串与正则表达式匹配,仅在匹配成功时调用parseInt()。我以最有效的方式使用了正则表达式(即在进入循环之前创建Pattern和Matcher对象),并且没有打印或保存异常的堆栈跟踪。 对于一万个字符串的列表,如果它们都是有效数字,parseInt()方法的速度是正则表达式方法的四倍。但是,如果只有80%的字符串有效,正则表达式的速度是parseInt()的两倍。如果20%是有效的,这意味着异常在80%的时间里被抛出并捕获,那么正则表达式的速度大约是parseInt()的20倍。 考虑到正则表达式方法处理有效字符串两次:一次用于匹配,另一次用于parseInt(),我对结果感到惊讶。但抛出和捕获异常远远弥补了这一点。这种情况在现实世界中不太可能经常发生,但如果发生了,你绝对不应该使用异常捕获技术。但是,如果您只验证用户输入或类似的东西,请务必使用parseInt()方法。 |
|
|
7
8
不知道这些主题是否相关,但我曾经想依靠当前线程的堆栈跟踪来实现一个技巧:我想发现在实例化类中触发实例化的方法的名称(是的,这个想法很疯狂,我完全放弃了)。所以我发现打电话
所以Java
但让我告诉你另一个故事。..
在Scala中,一些功能特性是使用JVM编译的
所以我调整了上面的测试(循环次数减少了十次,我的机器有点慢:):
因此,结果如下:
你看,两者之间的唯一区别
|
|
|
8
8
我认为第一篇文章将遍历调用堆栈并创建堆栈跟踪的行为称为代价高昂的部分,而第二篇文章没有这么说,我认为这是对象创建中代价最高的部分。约翰·罗斯 an article where he describes different techniques for speeding up exceptions (预分配和重用异常、没有堆栈跟踪的异常等) 但我仍然认为,这应该被视为一种必要的邪恶,一种最后的手段。John这样做的原因是为了模拟JVM中尚未提供的其他语言的特性。您不应该养成使用异常进行控制流的习惯。尤其不是出于性能原因!正如你在第2条中提到的,你有可能以这种方式掩盖代码中的严重错误,而且对于新程序员来说,维护起来会更困难。 Java中的微基准标记出乎意料地难以正确处理(有人告诉我),尤其是当你进入JIT领域时,所以我真的怀疑在现实生活中使用异常比“返回”更快。例如,我怀疑你的测试中有2到5个堆栈帧?现在想象一下,您的代码将被JBoss部署的JSF组件调用。现在,您可能有一个长达几页的堆栈跟踪。 也许你可以发布你的测试代码? |
|
|
9
5
我用JVM 1.5做了一些性能测试,使用异常至少慢了2倍。平均而言:一个微不足道的小方法的执行时间增加了两倍多(3倍),除了异常。一个必须捕获异常的微不足道的小循环的自我时间增加了2倍。 我在生产代码和微观基准测试中也看到了类似的数字。 例外情况应明确 不是 用于任何经常被调用的东西。每秒抛出数千个异常会造成巨大的瓶颈。 例如,使用“Integer.ParseInt(…)”查找一个非常大的文本文件中的所有错误值——这是一个非常糟糕的主意。(我见过这种实用方法 杀死 生产代码性能) 使用异常在用户GUI窗体上报告错误值,从性能角度来看可能没那么糟糕。 无论这是否是一个好的设计实践,我都会遵循规则:如果错误是正常的/预期的,那么使用返回值。如果异常,请使用异常。例如:读取用户输入,错误值是正常的——使用错误代码。将值传递给内部实用程序函数,应通过调用代码过滤坏值——使用异常。 |
|
|
10
4
Java和C#中的异常性能还有待提高。 作为程序员,这迫使我们遵循“异常应该很少发生”的规则,仅仅是出于实际的性能原因。 然而,作为计算机科学家,我们应该反抗这种有问题的状态。编写函数的人通常不知道函数被调用的频率,也不知道成功或失败的可能性更大。只有呼叫者有此信息。试图避免异常会导致API idom不明确,在某些情况下,我们只有干净的异常版本,在其他情况下,会出现快速但缓慢的返回值错误,而在其他情况中,我们最终会同时出现这两种错误。库实现者可能必须编写和维护两个版本的API,调用者必须决定在每种情况下使用两个版本中的哪一个。 这有点乱。如果异常具有更好的性能,我们可以避免这些笨拙的习惯用法,并按照预期使用异常。..作为结构化错误返回工具。 我真的很想看到使用更接近返回值的技术来实现异常机制,这样我们的性能就可以更接近于返回值。因为这是我们在性能敏感代码中恢复的内容。 这是一个代码示例,用于比较异常性能和错误返回值性能。 公共类TestIt{
} 结果如下:
与基线空调用相比,检查和传播返回值确实会增加一些成本,而且成本与调用深度成正比。在调用链深度为8时,错误返回值检查版本比不检查返回值的基线版本慢约27%。 相比之下,异常性能不是调用深度的函数,而是异常频率的函数。然而,随着异常频率的增加,下降幅度要大得多。在只有25%的错误频率下,代码的运行速度慢了24倍。错误频率为100%时,异常版本几乎慢100倍。 在我看来,这表明我们在异常实现中可能做出了错误的权衡。异常可能会更快,要么避免代价高昂的跟踪遍历,要么直接将其转换为编译器支持的返回值检查。在他们这样做之前,当我们想让代码快速运行时,我们只能避开他们。 |
|
11
3
HotSpot完全有能力删除系统生成的异常的异常代码,只要它都是内联的。但是,显式创建的异常和未删除的异常会花费大量时间创建堆栈跟踪。以(权力)否决
|
|
|
12
2
关于异常性能的精彩帖子是: https://shipilev.net/blog/2014/exceptional-performance/ 实例化与重用现有,有堆栈跟踪和没有堆栈跟踪等:
根据堆栈轨迹的深度:
有关其他详细信息(包括JIT的x64汇编程序),请阅读原始博客文章。 这意味着Hibernate/Spring/etc EE由于异常(xD)而运行缓慢。
通过重写应用程序控制流来避免异常(返回错误作为
|
|
13
2
即使抛出异常并不慢,为正常程序流抛出异常仍然是一个坏主意。以这种方式使用它类似于GOTO。.. 不过,我想这并没有真正回答问题。我想,抛出异常速度慢的“传统”智慧在早期的java版本中是正确的(<1.4)。创建异常需要VM创建整个堆栈跟踪。自那以后,VM发生了很多变化,以加快速度,这可能是一个得到改进的领域。 |
|
14
1
只需将Integer.parseInt与以下方法进行比较,在数据不可解析的情况下,该方法只返回默认值,而不是抛出Exception:
只要将这两种方法应用于“有效”数据,它们的工作速度就会大致相同(即使Integer.parseInt能够处理更复杂的数据)。但是,一旦您尝试解析无效数据(例如解析“abc”1.000.000次),性能差异应该是必不可少的。 |
|
|
15
1
在JDK 15上,使用附带的代码,我得到了与@Mecki测试用例完全不同的结果。这基本上是在5个循环中运行代码,第一个循环稍短,以便给VM一些时间进行预热。 结果:
|
|
|
16
0
我更改了@Mecki上面的答案,让method1在调用方法中返回一个布尔值和一个check,因为你不能把Exception替换为空。经过两次运行,方法1仍然是最快的,或者与方法2一样快。 这是代码的快照:
结果: 跑步1
跑步2
|
|
|
17
-3
我对异常速度与编程检查数据的看法。 许多类都有字符串到值转换器(扫描器/解析器),以及受人尊敬和知名的库;) 通常有形式
异常名称只是一个例子,通常是未选中的(运行时),所以throws声明只是我的图片 有时存在第二种形式:
从不投掷 当第二个输入不可用时(或者程序员阅读的文档太少,只使用第一个),用正则表达式编写这样的代码。正则表达式很酷,政治正确等:
有了这段代码,程序员就不必为异常付出代价。但是,正则表达式的成本总是很高,而异常的成本有时很低。 我几乎总是在这种情况下使用
不用分析stacktrace等,我相信听了你的讲座后速度相当快。 不要害怕例外 |
|
|
18
-6
为什么异常的返回速度要比正常的返回速度慢? 只要你不将堆栈跟踪打印到终端,将其保存到文件或类似文件中,catch块就不会比其他代码块做更多的工作。所以,我无法想象为什么“throw new my_cool_error()”会那么慢。 这个问题问得好,我期待着关于这个话题的更多信息! |
|
|
user29759326 · 如何返回递归函数中的最后一个值? 1 年前 |
|
|
malife89 · 将java中的字符串读取为正确的日期格式 1 年前 |
|
|
Tim · 在java中,有没有更快的方法将字节数组写入文件? 1 年前 |
|
|
rudraraj · java中未声明最终变量 1 年前 |
|
|
Bala Ji · 以下BFS的实施效率如何? 1 年前 |