代码之家  ›  专栏  ›  技术社区  ›  John Ellinwood

Java异常有多慢?

  •  432
  • John Ellinwood  · 技术社区  · 17 年前

    问:Java中的异常处理真的很慢吗?

    传统观点以及许多谷歌搜索结果都认为,Java中的正常程序流不应该使用异常逻辑。通常给出两个原因,

    1. 它真的很慢,甚至比常规代码慢一个数量级(给出的原因各不相同),

    1. 这很混乱,因为人们只希望在异常代码中处理错误。

    这个问题是关于#1的。

    例如, this page 将Java异常处理描述为“非常缓慢”,并将这种缓慢与异常消息字符串的创建联系起来——“然后将此字符串用于创建抛出的异常对象。这并不快。” Effective Exception Handling in Java 说“原因在于异常处理的对象创建方面,这使得抛出异常本身就很慢”。另一个原因是堆栈跟踪生成会减慢它的速度。

    我的测试(在32位Linux上使用Java 1.6.0_07、Java HotSpot 10.0)表明,异常处理并不比常规代码慢。我尝试在循环中运行一个方法来执行一些代码。在方法的末尾,我使用布尔值来指示是否 这样实际处理是相同的。我尝试以不同的顺序运行这些方法,并平均我的测试时间,认为这可能是JVM正在预热。在我所有的测试中,投掷速度至少和回击速度一样快,如果不是更快的话(快3.1%)。我完全接受我的测试出错的可能性,但在过去的一两年里,我没有看到任何代码示例、测试比较或结果显示Java中的异常处理实际上很慢。

    让我走上这条路的是我需要使用的API,它将抛出异常作为正常控制逻辑的一部分。我想纠正他们的用法,但现在我可能无法做到。我是否应该赞扬他们的前瞻性思维?

    在纸上 Efficient Java exception handling in just-in-time compilation ,作者认为,即使没有抛出异常,仅存在异常处理程序也足以阻止JIT编译器正确优化代码,从而减缓其速度。我还没有检验这个理论。

    17 回复  |  直到 6 年前
        1
  •  360
  •   Pr0methean    11 年前

    这取决于如何实现例外。最简单的方法是使用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块的优势。

    请参阅以下测试代码:

    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
    

    试块的减速太小,无法排除背景进程等混杂因素。但接球块杀死了一切,使速度慢了66倍!

    正如我所说的,如果你把try/catch和throw都放在同一个方法(method3)中,结果不会那么糟糕,但这是一种特殊的JIT优化,我不会依赖它。即使使用这种优化,投掷仍然很慢。所以我不知道你在这里想做什么,但肯定有比使用try/catch/stw更好的方法。

        2
  •  273
  •   Hot Licks    14 年前

    仅供参考,我扩展了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
    

    前3个与Mecki的相同(我的笔记本电脑明显较慢)。

    方法4与方法3相同,除了它创建了一个 new Integer(1) 而不是做 throw new Exception() .

    method5与method3类似,除了它创建了 new Exception() 不扔。

    method6与method3类似,只是它抛出了一个预先创建的异常(一个实例变量),而不是创建一个新的异常。

    在Java中,抛出异常的大部分费用是收集堆栈跟踪所花费的时间,这发生在创建异常对象时。抛出异常的实际成本虽然很大,但远低于创建异常的成本。

        3
  •  80
  •   Yoon5oo JBE    8 年前

    Aleksey Shipilv做了一个 very thorough analysis 其中,他在各种条件组合下对Java异常进行了基准测试:

    • 新创建的异常与预先创建的异常
    • 堆栈跟踪启用与禁用
    • 请求堆栈跟踪与从未请求堆栈跟踪
    • 在最高级别被抓住vs在每个级别重新生长vs在每个层面被束缚/包裹
    • 不同级别的Java调用堆栈深度
    • 无内联优化vs极端内联vs默认设置
    • 用户定义字段读取与不读取

    他还将它们与在不同错误频率下检查错误代码的性能进行了比较。

    结论(逐字引用自他的帖子)是:

    1. 真正的例外情况表现得非常出色。 如果你按设计使用它们,并且只在常规代码处理的大量非异常情况中传达真正的异常情况,那么使用异常就是性能的胜利。

    2. 异常的性能成本有两个主要组成部分: 烟囱痕迹施工 当实例化Exception时 栈展开 在抛出异常期间。

    3. 烟囱跟踪施工成本与烟囱深度成正比 在异常实例化的时刻。这已经很糟糕了,因为地球上有谁知道这种投掷方法会被调用的堆叠深度?即使关闭堆栈跟踪生成和/或缓存异常,也只能摆脱这部分性能成本。

    4. 堆栈展开成本取决于我们在编译代码中使异常处理程序更接近的幸运程度。 仔细构造代码以避免深入的异常处理程序查找可能会帮助我们变得更幸运。

    5. 如果我们消除这两种影响,异常的性能成本就是本地分支的性能成本。 无论听起来多么漂亮,这并不意味着你应该使用Exceptions作为通常的控制流,因为在这种情况下 你完全受制于优化编译器! 您应该只在真正特殊的情况下使用它们,在这种情况下,异常频率 分期偿还 引发实际异常可能带来的不幸代价。

    6. 乐观的经验法则似乎是 10^-4 异常的频率非常高。当然,这取决于异常本身的权重、异常处理程序中采取的确切操作等。

    结果是,当没有抛出异常时,您不需要支付成本,因此当异常情况足够罕见时,异常处理比使用 if 每一次。全文非常值得一读。

        4
  •  41
  •   Yoon5oo JBE    8 年前

    不幸的是,我的答案太长了,无法在这里发布。所以,让我在这里总结一下,并推荐你参考 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
  •   manikanta    7 年前

    我扩展了以下给出的答案 @Mecki @incarnate ,Java没有堆栈跟踪填充。

    使用Java 7+,我们可以 Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) 。但对于Java6,请参见 my answer for this question

    // 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
    

    因此,与抛出异常的方法相比,返回值的方法仍然更快。IMHO,我们不能设计一个清晰的API,只使用成功和成功的返回类型;错误流。在没有堆栈跟踪的情况下抛出异常的方法比普通异常快4-5倍。

    编辑:NoStackTraceThrowable.java 谢谢@Greg

    public class NoStackTraceThrowable extends Throwable { 
        public NoStackTraceThrowable() { 
            super("my special throwable", null, false, false);
        }
    }
    
        6
  •  8
  •   Alan Moore Chris Ballance    17 年前

    不久前,我编写了一个类来测试使用两种方法将字符串转换为整数的相对性能:(1)调用Integer.parseInt()并捕获异常,或(2)将字符串与正则表达式匹配,仅在匹配成功时调用parseInt()。我以最有效的方式使用了正则表达式(即在进入循环之前创建Pattern和Matcher对象),并且没有打印或保存异常的堆栈跟踪。

    对于一万个字符串的列表,如果它们都是有效数字,parseInt()方法的速度是正则表达式方法的四倍。但是,如果只有80%的字符串有效,正则表达式的速度是parseInt()的两倍。如果20%是有效的,这意味着异常在80%的时间里被抛出并捕获,那么正则表达式的速度大约是parseInt()的20倍。

    考虑到正则表达式方法处理有效字符串两次:一次用于匹配,另一次用于parseInt(),我对结果感到惊讶。但抛出和捕获异常远远弥补了这一点。这种情况在现实世界中不太可能经常发生,但如果发生了,你绝对不应该使用异常捕获技术。但是,如果您只验证用户输入或类似的东西,请务必使用parseInt()方法。

        7
  •  8
  •   BorisOkunskiy    15 年前

    不知道这些主题是否相关,但我曾经想依靠当前线程的堆栈跟踪来实现一个技巧:我想发现在实例化类中触发实例化的方法的名称(是的,这个想法很疯狂,我完全放弃了)。所以我发现打电话 Thread.currentThread().getStackTrace() 极其 缓慢(由于本地 dumpThreads 它内部使用的方法)。

    所以Java Throwable 相应地,有一个本地方法 fillInStackTrace 我认为凶手- catch 前面描述的块以某种方式触发了此方法的执行。

    但让我告诉你另一个故事。..

    在Scala中,一些功能特性是使用JVM编译的 ControlThrowable ,延伸 可抛出 并覆盖其 fillInStackTrace 按照以下方式:

    override def fillInStackTrace(): Throwable = this
    

    所以我调整了上面的测试(循环次数减少了十次,我的机器有点慢:):

    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 method4 他们抛出不同类型的例外。是啊, 方法4 仍然比 method1 method2 ,但这种差异更容易被接受。

        8
  •  8
  •   jrudolph    12 年前

    我认为第一篇文章将遍历调用堆栈并创建堆栈跟踪的行为称为代价高昂的部分,而第二篇文章没有这么说,我认为这是对象创建中代价最高的部分。约翰·罗斯 an article where he describes different techniques for speeding up exceptions (预分配和重用异常、没有堆栈跟踪的异常等)

    但我仍然认为,这应该被视为一种必要的邪恶,一种最后的手段。John这样做的原因是为了模拟JVM中尚未提供的其他语言的特性。您不应该养成使用异常进行控制流的习惯。尤其不是出于性能原因!正如你在第2条中提到的,你有可能以这种方式掩盖代码中的严重错误,而且对于新程序员来说,维护起来会更困难。

    Java中的微基准标记出乎意料地难以正确处理(有人告诉我),尤其是当你进入JIT领域时,所以我真的怀疑在现实生活中使用异常比“返回”更快。例如,我怀疑你的测试中有2到5个堆栈帧?现在想象一下,您的代码将被JBoss部署的JSF组件调用。现在,您可能有一个长达几页的堆栈跟踪。

    也许你可以发布你的测试代码?

        9
  •  5
  •   James Schek    17 年前

    我用JVM 1.5做了一些性能测试,使用异常至少慢了2倍。平均而言:一个微不足道的小方法的执行时间增加了两倍多(3倍),除了异常。一个必须捕获异常的微不足道的小循环的自我时间增加了2倍。

    我在生产代码和微观基准测试中也看到了类似的数字。

    例外情况应明确 不是 用于任何经常被调用的东西。每秒抛出数千个异常会造成巨大的瓶颈。

    例如,使用“Integer.ParseInt(…)”查找一个非常大的文本文件中的所有错误值——这是一个非常糟糕的主意。(我见过这种实用方法 杀死 生产代码性能)

    使用异常在用户GUI窗体上报告错误值,从性能角度来看可能没那么糟糕。

    无论这是否是一个好的设计实践,我都会遵循规则:如果错误是正常的/预期的,那么使用返回值。如果异常,请使用异常。例如:读取用户输入,错误值是正常的——使用错误代码。将值传递给内部实用程序函数,应通过调用代码过滤坏值——使用异常。

        10
  •  4
  •   David Jeske    14 年前

    Java和C#中的异常性能还有待提高。

    作为程序员,这迫使我们遵循“异常应该很少发生”的规则,仅仅是出于实际的性能原因。

    然而,作为计算机科学家,我们应该反抗这种有问题的状态。编写函数的人通常不知道函数被调用的频率,也不知道成功或失败的可能性更大。只有呼叫者有此信息。试图避免异常会导致API idom不明确,在某些情况下,我们只有干净的异常版本,在其他情况下,会出现快速但缓慢的返回值错误,而在其他情况中,我们最终会同时出现这两种错误。库实现者可能必须编写和维护两个版本的API,调用者必须决定在每种情况下使用两个版本中的哪一个。

    这有点乱。如果异常具有更好的性能,我们可以避免这些笨拙的习惯用法,并按照预期使用异常。..作为结构化错误返回工具。

    我真的很想看到使用更接近返回值的技术来实现异常机制,这样我们的性能就可以更接近于返回值。因为这是我们在性能敏感代码中恢复的内容。

    这是一个代码示例,用于比较异常性能和错误返回值性能。

    公共类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);              
            }
        }
    }
    

    }

    结果如下:

    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时,错误返回值检查版本比不检查返回值的基线版本慢约27%。

    相比之下,异常性能不是调用深度的函数,而是异常频率的函数。然而,随着异常频率的增加,下降幅度要大得多。在只有25%的错误频率下,代码的运行速度慢了24倍。错误频率为100%时,异常版本几乎慢100倍。

    在我看来,这表明我们在异常实现中可能做出了错误的权衡。异常可能会更快,要么避免代价高昂的跟踪遍历,要么直接将其转换为编译器支持的返回值检查。在他们这样做之前,当我们想让代码快速运行时,我们只能避开他们。

        11
  •  3
  •   Tom Hawtin - tackline    17 年前

    HotSpot完全有能力删除系统生成的异常的异常代码,只要它都是内联的。但是,显式创建的异常和未删除的异常会花费大量时间创建堆栈跟踪。以(权力)否决 fillInStackTrace 看看这会如何影响性能。

        12
  •  2
  •   user38051    17 年前

    关于异常性能的精彩帖子是:

    https://shipilev.net/blog/2014/exceptional-performance/

    实例化与重用现有,有堆栈跟踪和没有堆栈跟踪等:

    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
    

    根据堆栈轨迹的深度:

    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)而运行缓慢。

    通过重写应用程序控制流来避免异常(返回错误作为 return )将应用程序的性能提高10x-100x,具体取决于您抛出它们的频率))

        13
  •  2
  •   Adam Paynter    15 年前

    即使抛出异常并不慢,为正常程序流抛出异常仍然是一个坏主意。以这种方式使用它类似于GOTO。..

    不过,我想这并没有真正回答问题。我想,抛出异常速度慢的“传统”智慧在早期的java版本中是正确的(<1.4)。创建异常需要VM创建整个堆栈跟踪。自那以后,VM发生了很多变化,以加快速度,这可能是一个得到改进的领域。

        14
  •  1
  •   john16384    5 年前

    只需将Integer.parseInt与以下方法进行比较,在数据不可解析的情况下,该方法只返回默认值,而不是抛出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;
      }
    

    只要将这两种方法应用于“有效”数据,它们的工作速度就会大致相同(即使Integer.parseInt能够处理更复杂的数据)。但是,一旦您尝试解析无效数据(例如解析“abc”1.000.000次),性能差异应该是必不可少的。

        15
  •  1
  •   gavenkoa    5 年前

    在JDK 15上,使用附带的代码,我得到了与@Mecki测试用例完全不同的结果。这基本上是在5个循环中运行代码,第一个循环稍短,以便给VM一些时间进行预热。

    结果:

    Loop 1 10000 cycles
    method1 took 1 ms, result was 2
    method2 took 0 ms, result was 2
    method3 took 22 ms, result was 2
    method4 took 22 ms, result was 2
    method5 took 24 ms, result was 2
    Loop 2 10000000 cycles
    method1 took 39 ms, result was 2
    method2 took 39 ms, result was 2
    method3 took 1558 ms, result was 2
    method4 took 1640 ms, result was 2
    method5 took 1717 ms, result was 2
    Loop 3 10000000 cycles
    method1 took 49 ms, result was 2
    method2 took 48 ms, result was 2
    method3 took 126 ms, result was 2
    method4 took 88 ms, result was 2
    method5 took 87 ms, result was 2
    Loop 4 10000000 cycles
    method1 took 34 ms, result was 2
    method2 took 34 ms, result was 2
    method3 took 33 ms, result was 2
    method4 took 98 ms, result was 2
    method5 took 58 ms, result was 2
    Loop 5 10000000 cycles
    method1 took 34 ms, result was 2
    method2 took 33 ms, result was 2
    method3 took 33 ms, result was 2
    method4 took 48 ms, result was 2
    method5 took 49 ms, result was 2
    
    package hs.jfx.eventstream.api;
    
    public class Snippet {
      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();
          }
      }
    
      private static final NoStackTraceRuntimeException E = new NoStackTraceRuntimeException();
    
      // This one will regularly throw one
      public void method3(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 E;
          }
      }
    
      // 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) {
        for(int k = 0; k < 5; k++) {
          int cycles = 10000000;
          if(k == 0) {
            cycles = 10000;
            try {
              Thread.sleep(500);
            }
            catch(InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
          }
          System.out.println("Loop " + (k + 1) + " " + cycles + " cycles");
          int i;
          long l;
          Snippet t = new Snippet();
    
          l = System.currentTimeMillis();
          t.reset();
          for (i = 1; i < cycles; 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 < cycles; 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 < cycles; i++) {
              try {
                  t.method3(i);
              } catch (NoStackTraceRuntimeException e) {
                // always comes here
              }
          }
          l = System.currentTimeMillis() - l;
          System.out.println(
              "method3 took " + l + " ms, result was " + t.getValue()
          );
    
    
          l = System.currentTimeMillis();
          t.reset();
          for (i = 1; i < cycles; i++) {
              try {
                  t.method4(i);
              } catch (NoStackTraceThrowable e) {
                // always comes 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 < cycles; i++) {
              try {
                  t.method5(i);
              } catch (RuntimeException e) {
                // always comes here
              }
          }
          l = System.currentTimeMillis() - l;
          System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
        }
      }
    
      public static class NoStackTraceRuntimeException extends RuntimeException {
        public NoStackTraceRuntimeException() {
            super("my special throwable", null, false, false);
        }
      }
    
      public static class NoStackTraceThrowable extends Throwable {
        public NoStackTraceThrowable() {
            super("my special throwable", null, false, false);
        }
      }
    }
    
    
        16
  •  0
  •   inder    14 年前

    我更改了@Mecki上面的答案,让method1在调用方法中返回一个布尔值和一个check,因为你不能把Exception替换为空。经过两次运行,方法1仍然是最快的,或者与方法2一样快。

    这是代码的快照:

    // 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

    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
    
        17
  •  -3
  •   Jacek Cz    10 年前

    我对异常速度与编程检查数据的看法。

    许多类都有字符串到值转换器(扫描器/解析器),以及受人尊敬和知名的库;)

    通常有形式

    class Example {
    public static Example Parse(String input) throws AnyRuntimeParsigException
    ...
    }
    

    异常名称只是一个例子,通常是未选中的(运行时),所以throws声明只是我的图片

    有时存在第二种形式:

    public static Example Parse(String input, Example defaultValue)
    

    从不投掷

    当第二个输入不可用时(或者程序员阅读的文档太少,只使用第一个),用正则表达式编写这样的代码。正则表达式很酷,政治正确等:

    Xxxxx.regex(".....pattern", src);
    if(ImTotallySure)
    {
      Example v = Example.Parse(src);
    }
    

    有了这段代码,程序员就不必为异常付出代价。但是,正则表达式的成本总是很高,而异常的成本有时很低。

    我几乎总是在这种情况下使用

    try { parse } catch(ParsingException ) // concrete exception from javadoc
    {
    }
    

    不用分析stacktrace等,我相信听了你的讲座后速度相当快。

    不要害怕例外

        18
  •  -6
  •   ljs.dev    12 年前

    为什么异常的返回速度要比正常的返回速度慢?

    只要你不将堆栈跟踪打印到终端,将其保存到文件或类似文件中,catch块就不会比其他代码块做更多的工作。所以,我无法想象为什么“throw new my_cool_error()”会那么慢。

    这个问题问得好,我期待着关于这个话题的更多信息!