对正常的代码流使用异常是很糟糕的 - 它很慢,代码质量很差,而且从第1天开始我们都已经把这个鼓起来了。至少我有!这也是有道理的,异常会在每次调用时生成堆栈跟踪,堆栈跟踪需要很长时间才能生成,因此抛出和捕获的异常比等效的if语句慢得多。
所以我决定做一个快速的例子来证明这一点。
public static void main(String[] args) throws Exception {
Object[] objs = new Object[500000];
for (int i = 0; i < objs.length; i++) {
if (Math.random() < 0.5) {
objs[i] = new Object();
}
}
long cur = System.currentTimeMillis();
for (Object o : objs) {
try {
System.out.print(o.getClass());
}
catch (Exception e) {
System.out.print("null");
}
}
System.err.println("Try / catch: "+(System.currentTimeMillis() - cur));
cur = System.currentTimeMillis();
for (Object o : objs) {
if(o==null) {
System.out.print("null");
}
else {
System.out.print(o.getClass());
}
}
System.err.println("If: "+(System.currentTimeMillis() - cur));
}
然而,在运行代码时,我很震惊地看到以下内容:
Try / catch: 11204
If: 11953
我再次运行代码,这次“如果”更快:
Try / catch: 12188
If: 12187
......但只有一个ms。
这一切都在服务器VM上运行,我知道大部分时间都会被print()
语句占用,有些运行显示是否比try / catch快一点。但无论如何,他们肯定不应该接近?如何生成堆栈跟踪比单个if语句更快?
编辑:澄清一下,这个问题纯粹是学术问题 - 我完全清楚地知道正常代码流的异常是坏的,永远不会这样做!
答案 0 :(得分:4)
最近在Java专家新闻中讨论了这个问题:http://www.javaspecialists.eu/archive/Issue187.html
简短回答:它不会每次都生成堆栈跟踪,而是缓存异常的实例。
答案 1 :(得分:2)
因为异常抛出点和捕获点在同一范围内,并且JIT可以证明您从未真正查看堆栈跟踪,所以可以将异常优化到单个分支中。您可以通过显式抛出自定义类型的异常来避免这种情况,理想情况是跨函数调用边界。为了控制分配成本,在非抛出的情况下,一定要构造一个所述异常对象的实例(理想情况下返回它,以确保转义分析不能将其转换为堆栈分配)。
那就是说,JIT可以做各种奇怪的事情。最好尝试尽可能与Real Code相似的测试用例,而不是像这样的假设情况,因此您可以在JIT可以应用于您的实际代码的任何优化之后看到基准测试结果。
答案 2 :(得分:1)
我相信早期版本的Java中的堆栈跟踪速度非常慢。事实上,我编写了一个循环英国网格地图1000 x 800的程序,并使用if语句,程序在10分钟内运行,在异常和捕获中运行,它在2小时内运行。这是10年前的事。
最近,JVM在处理try-catch方面效率更高。
我也有,“例外是针对特殊情况”从一开始就鼓吹我,当我看到播放框架使用异常作为全局goto语句时,为了执行控制流程,我最初感到震惊。但是,在实际工作场景中运行Play非常快。这是对我的另一种误解完全被吹走了,并且开始思考许多使用异常作为设计模式的新方法。
答案 3 :(得分:1)
基本原因是堆栈跟踪数据的填充现在被延迟到实际需要,最初在创建异常时填充它。
您可能希望将堆栈跟踪打印到ByteArrayOutputStream,以查看堆栈跟踪实际上使用的位置。
答案 4 :(得分:1)
值得注意的是,虽然许多人考虑将流量控制的例外用作“不良形式”,但它并不一定慢。例如,您可以选择
对于值较少存在的情况,test-each-iteration版本可能会更快。对于价值更频繁存在的情况,捕获失败版本可能会更快。不可否认,在两者之间做出选择假设您对数据有很好的理解,并且希望可以运行一个分析器来查看在两者之间切换的影响。
所有这一切,如果你真的不知道抛出的异常会更快,你可能会更好地使用每次迭代测试版本,因为它(对于很多人来说,它更明显地做了什么。
答案 5 :(得分:0)
少数事情
话虽如此:例外情况并不缓慢。打印堆栈跟踪是另一个问题。
额外奖励:空检查很快,不使用异常时空陷阱也是如此
答案 6 :(得分:0)
但是,速度并不是在正常流程中不使用异常的唯一原因。例外是一种表明方法合同被破坏的机制。如果您开始使用异常作为流量控制,那么它就会失败。通常,您可以在代码中写几行以避免异常。