【问题标题】:What are the effects of exceptions on performance in Java?Java 中异常对性能的影响是什么?
【发布时间】:2010-09-22 21:07:35
【问题描述】:

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

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

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

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

这个问题是关于#1的。

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

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

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

在论文Efficient Java exception handling in just-in-time compilation 中,作者建议仅存在异常处理程序,即使没有抛出异常,也足以阻止 JIT 编译器正确优化代码,从而减慢它的速度。我还没有测试过这个理论。

【问题讨论】:

  • 我知道您不是在询问 2),但您确实应该认识到,对程序流使用异常并不比使用 GOTO 更好。有些人为 goto 辩护,有些人会为你所说的辩护,但如果你问一个已经实现并维护了一段时间的人,他们会告诉你,两者都是糟糕的难以维护的设计实践(并且可能会诅咒认为自己足够聪明,可以决定使用它们的人的名字)。
  • Bill,声称在程序流中使用异常并不比使用 GOTO 好,也并不比在程序流中使用条件和循环并不比使用 GOTO 好。这是一条红鲱鱼。解释你自己。异常可以并且有效地用于其他语言的程序流。例如,惯用的 Python 代码经常使用异常。我可以并且已经维护了以这种方式使用异常的代码(虽然不是 Java),而且我认为它本身没有任何问题。
  • @mmalone 对正常的控制流使用异常在 Java 中是个坏主意,因为 范式选择就是这样完成的。阅读 Bloch EJ2 - 他清楚地指出,引用,(第 57 项)exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow - 给出了完整和广泛的解释为什么。他是编写 Java lib 的人。因此,他是定义类的 API 契约的人。 /就这个问题同意 Bill K。
  • @OndraŽižka 如果某些框架这样做(在非异常情况下使用异常),它的设计存在缺陷和破坏,破坏了语言的异常类契约。仅仅因为有些人编写糟糕的代码并不能让它变得不那么糟糕。
  • 除了 stackoverflow.com 的创建者之外,没有其他人对异常是错误的。软件开发的黄金法则是永远不要让简单的复杂和笨拙。他写道:“确实,当您进行良好的错误检查时,应该是一个简单的 3 行程序通常会发展到 48 行,但这就是生活,......”这是对纯粹性的追求,而不是简单性。

标签: java performance exception


【解决方案1】:

这取决于异常是如何实现的。最简单的方法是使用 setjmp 和 longjmp。这意味着 CPU 的所有寄存器都被写入堆栈(这已经需要一些时间)并且可能需要创建一些其他数据......所有这些都已经发生在 try 语句中。 throw 语句需要展开堆栈并恢复所有寄存器的值(以及 VM 中可能的其他值)。所以 try 和 throw 同样慢,而且相当慢,但是如果没有抛出异常,在大多数情况下退出 try 块不需要任何时间(因为所有东西都放在堆栈上,如果方法存在,它会自动清理)。

Sun 和其他人认识到,这可能不是最理想的,当然随着时间的推移,VM 会变得越来越快。还有另一种实现异常的方法,它使 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

try 块的减速太小,无法排除后台进程等混杂因素。但是 catch 块杀死了一切,让它慢了 66 倍!

正如我所说,如果您将 try/catch 和 throw 全部放在同一个方法(方法 3)中,结果不会那么糟糕,但这是我不会依赖的特殊 JIT 优化。即使使用这种优化,抛出仍然很慢。所以我不知道你在这里要做什么,但肯定有比使用 try/catch/throw 更好的方法。

【讨论】:

  • 很好的答案,但我想补充一点,据我所知,System.nanoTime() 应该用于测量性能,而不是 System.currentTimeMillis()。
  • @SimonAndréForsberg nanoTime() 需要 Java 1.5,而我在用于编写上述代码的系统上只有 Java 1.4。而且它在实践中并没有起到很大的作用。两者之间的唯一区别是,一个是纳秒,另一个是毫秒,nanoTime 不受时钟操作的影响(这是无关紧要的,除非您或系统进程在测试代码运行的那一刻准确地修改了系统时钟)。不过一般来说你是对的,nanoTime 当然是更好的选择。
  • 确实应该注意的是,您的测试是一个极端情况。对于带有try 块但没有throw 的代码,您显示出非常小的性能损失。您的throw 测试在50% 的时间 中抛出异常,它通过try。这显然是失败不是异常的情况。将其减少到仅 10% 会大大降低性能损失。这种测试的问题在于它鼓励人们完全停止使用异常。使用异常来处理异常情况,其性能比您的测试显示的要好得多。
  • @Glide 投掷不像干净的return。它在主体中间的某个地方留下了一个方法,甚至可能在一个操作的中间(到目前为止只完成了 50%)并且catch 块可能是向上的 20 个堆栈帧(一个方法有一个 @987654332 @block,调用method1,调用method1,调用method2,调用mehtod3,...,在method20中一个操作中抛出异常)。堆栈必须向上展开 20 帧,所有未完成的操作必须撤消(操作不能完成一半)并且 CPU 寄存器需要处于干净状态。这都需要时间。
  • 在 jdk 1.8.0_181 上,我得到这些结果表明性能有所提高:方法 1 耗时 928 毫秒,结果为 2 | method2 耗时 1098 毫秒,结果为 2 |方法 3 耗时 26366 毫秒,结果为 2
【解决方案2】:

仅供参考,我扩展了 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 的一样(我的笔记本电脑明显比较慢)。

method4 与 method3 相同,只是它创建 new Integer(1) 而不是创建 throw new Exception()

method5 与 method3 类似,只是它创建 new Exception() 而不抛出它。

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

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

【讨论】:

  • +1 您的回答解决了核心问题 - 展开和跟踪堆栈所花费的时间,其次是抛出错误。我会选择这个作为最终答案。
  • 不错。约 70% 创建异常,约 30% 抛出异常。很好的信息。
  • @Basil - 你应该能够从上面的数字中弄清楚。
  • @HotLicks,这正是说明帖子中使用的 Java 版本很重要的原因
  • 我们可以注意到,在标准代码中,创建和抛出异常的情况很少见(我的意思是在运行时),如果不是这种情况,要么运行时条件非常糟糕,要么设计本身问题;在这两种情况下,性能都不是问题......
【解决方案3】:

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

  • 新创建的异常与预先创建的异常
  • 堆栈跟踪启用与禁用
  • 请求堆栈跟踪与从未请求
  • 在顶层捕获 vs 在每一层重新抛出 vs 在每一层被锁住/包裹
  • 各种级别的 Java 调用堆栈深度
  • 无内联优化 vs 极端内联 vs 默认设置
  • 用户定义的字段读取与未读取

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

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

  1. 真正的异常异常具有出色的性能。如果您按设计使用它们,并且只在常规代码处理的绝大多数非异常情况中传达真正的异常情况,那么使用异常是性能的胜利。

  2. 异常的性能成本有两个主要组成部分:堆栈跟踪构造(在实例化异常时)和堆栈展开在异常抛出期间。

    李>
  3. 堆栈跟踪构建成本与异常实例化时的堆栈深度成正比。这已经很糟糕了,因为地球上谁知道调用这个抛出方法的堆栈深度?即使关闭堆栈跟踪生成和/或缓存异常,也只能摆脱这部分性能成本。

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

  5. 如果我们消除这两种影响,异常的性能成本是本地分支的。无论听起来多么漂亮,这并不意味着您应该使用异常作为通常的控制流,因为在那种情况下您将受到优化编译器的摆布!您应该只在真正例外的情况下使用它们,在这种情况下,异常频率摊销可能带来的不幸成本实际的例外。

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

结果是,当不抛出异常时,您无需支付任何费用,因此当异常情况足够罕见时,异常处理比每次都使用if 更快。完整的帖子非常值得一读。

【讨论】:

    【解决方案4】:

    很遗憾,我的回答太长,无法在此处发布。所以让我在这里总结一下,并推荐您http://www.fuwjax.com/how-slow-are-java-exceptions/ 了解详细信息。

    这里真正的问题不是“与'永不失败的代码'相比,'失败报告为异常'有多慢?”正如您所接受的答复可能会让您相信的那样。相反,问题应该是“与以其他方式报告的故障相比,‘作为异常报告的故障’有多慢?”通常,报告失败的另外两种方法是使用标记值或使用结果包装器。

    Sentinel 值是在成功的情况下尝试返回一个类,在失败的情况下返回另一个类。您几乎可以将其视为返回异常而不是抛出异常。这需要一个与成功对象共享的父类,然后执行“instanceof”检查和几个强制转换以获得成功或失败信息。

    事实证明,冒着类型安全的风险,Sentinel 值比异常值快,但只有大约 2 倍。现在,这可能看起来很多,但这 2 倍仅涵盖了实施差异的成本。在实践中,这个因素要低得多,因为我们可能失败的方法比本页其他地方的示例代码中的一些算术运算符更有趣。

    另一方面,结果包装器根本不牺牲类型安全。它们将成功和失败信息包装在一个类中。因此,它们不是“instanceof”,而是为成功和失败对象提供“isSuccess()”和吸气剂。然而,结果对象比使用异常大约 2 倍。事实证明,每次创建一个新的包装对象比有时抛出异常要昂贵得多。

    最重要的是,异常是提供指示方法可能失败的方式的语言。没有其他方法可以仅通过 API 判断哪些方法应始终(大部分)有效,哪些方法应报告失败。

    异常比哨兵更安全,比结果对象更快,并且比任何一个都不令人惊讶。我不是建议 try/catch 替换 if/else,但异常是报告失败的正确方法,即使在业务逻辑中也是如此。

    也就是说,我想指出,我遇到的两种最常见的严重影响性能的方法是创建不必要的对象和嵌套循环。如果您可以选择创建异常或不创建异常,请不要创建异常。如果您可以在有时创建异常或一直创建另一个对象之间做出选择,请创建异常。

    【讨论】:

    • 我决定测试这三种实现的长期性能,并与一个检查失败而不报告的控制实现进行比较。该过程的失败率约为4%。测试的迭代针对其中一种策略调用过程 10000 次。每个策略都经过 1000 次测试,最后 900 次用于生成统计数据。以下是纳秒的平均时间: 控制 338 异常 429 结果 348 哨兵 345
    • 只是为了好玩,我在异常测试中禁用了 fillInStackTrace。现在是这个时代:控制 347 异常 351 结果 364 哨兵 355
    • Fuwjax,除非我遗漏了什么(我承认我只阅读了您的 SO 帖子,而不是您的博客帖子),您上面的两个 cmets 似乎与您的帖子相矛盾。我认为在您的基准测试中,较低的数字会更好,对吧?在这种情况下,在启用 fillInStackTrace 的情况下生成异常(这是默认和通常的行为),会导致比您描述的其他两种技术更慢的性能。我是否遗漏了什么,或者您是否真的发表评论来反驳您的帖子?
    • @Fuwjax - 避免您在此处提出的“艰难险阻”选择的方法是预分配一个代表“成功”的对象。通常也可以为常见的失败情况预先分配对象。然后只有在传回附加细节的极少数情况下,才会创建一个新对象。 (这是整数“错误代码”的 OO 等价物,加上一个单独的调用来获取最后一个错误的详细信息 - 这种技术已经存在了几十年。)
    • @Fuwjax 所以抛出异常不会通过您的帐户创建对象?不确定我是否理解这种推理。无论您是抛出异常还是返回结果对象,您都在创建对象。从这个意义上说,结果对象并不比抛出异常慢。
    【解决方案5】:

    我已经扩展了 @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
    

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

    编辑:NoStackTraceThrowable.java 感谢@Greg

    public class NoStackTraceThrowable extends Throwable { 
        public NoStackTraceThrowable() { 
            super("my special throwable", null, false, false);
        }
    }
    

    【讨论】:

    • 很有趣,谢谢。这是缺少的类声明:public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
    • 一开始你写了With Java 7+, we can use,但后来你写了Output with Java 1.6.0_45,所以这是Java 6或7的结果?
    • @WBAR 来自 Java 7,我们只需要使用 Throwable 具有 boolean writableStackTrace 参数的构造函数。但这在 Java 6 及更低版本中不存在。这就是我为 Java 6 及更低版本提供自定义实现的原因。所以上面的代码适用于 Java 6 及以下版本。请仔细阅读第 2 段的第 1 行。
    • @manikanta “恕我直言,我们无法设计一个清晰的 API,只使用成功和错误流的返回类型。”——我们可以,如果我们使用 Optionals/Results/也许和许多语言一样.
    • @Hejazzman 我同意。但是Optional 或类似的东西对 Java 来说有点晚了。在此之前,我们还使用了带有成功/错误标志的包装器对象。但这似乎有点 hacks,对我来说并不自然。
    【解决方案6】:

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

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

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

    【讨论】:

    • 你用的是哪个JVM? sun-jdk 6 还那么慢吗?
    • 在提交该答案之前,我将它挖出来并在 JDK 1.6u10 下再次运行,这些是我发布的结果。
    • 这非常非常有用!谢谢。对于我通常的用例,我确实需要解析用户输入(使用 Integer.ParseInt() 之类的东西),我希望 大多数时候用户输入是正确的,所以对于我的用例来说,它看起来像采取偶尔的异常命中是要走的路。
    【解决方案7】:

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

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

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

    在 Scala 中,一些功能特性在 JVM 中使用 ControlThrowable 编译,它扩展 Throwable 并以下列方式覆盖其 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
    

    你看,method3method4 之间的唯一区别是它们抛出了不同类型的异常。是的,method4 仍然比 method1method2 慢,但差异是可以接受的。

    【讨论】:

      【解决方案8】:

      我认为第一篇文章提到遍历调用堆栈和创建堆栈跟踪的行为是昂贵的部分,虽然第二篇文章没有说,但我认为这是对象创建中最昂贵的部分.约翰·罗斯有an article where he describes different techniques for speeding up exceptions。 (预分配和重用异常,没有堆栈跟踪的异常等)

      但是 - 我认为这应该被认为只是一种必要的邪恶,最后的手段。 John 这样做的原因是为了模拟 JVM 中(还没有)可用的其他语言的特性。您不应该养成使用控制流异常的习惯。尤其不是出于性能原因!正如您自己在 #2 中提到的那样,您可能会以这种方式掩盖代码中的严重错误,并且对于新程序员来说维护起来会更加困难。

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

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

      【讨论】:

        【解决方案9】:

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

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

        异常应该明确用于任何经常调用的东西。每秒抛出数千个异常会导致巨大的瓶颈。

        例如,使用“Integer.ParseInt(...)”来查找一个非常大的文本文件中的所有错误值——这是非常糟糕的主意。 (我已经在生产代码中看到了这种实用方法 kill 的性能)

        使用异常报告用户 GUI 表单上的错误值,从性能角度来看可能还不错。

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

        【讨论】:

        • 让我建议一些好的做法:如果您需要表单中的数字,而不是使用 Integer.valueOf(String),您应该考虑使用正则表达式匹配器。您可以预编译和重用该模式,因此制作匹配器很便宜。然而,在 GUI 表单上,有一个 isValid/validate/checkField 或者你有什么可能更清楚。此外,在 Java 8 中,我们有 Optional monad,因此请考虑使用它们。 (答案是 9 岁,但仍然如此!:p)
        【解决方案10】:

        Java 和 C# 中的异常性能还有很多不足之处。

        作为程序员,这迫使我们遵守“应不经常引起异常”的规则,只是出于实际性能原因。

        但是,作为计算机科学家,我们应该反对这种有问题的状态。编写函数的人通常不知道调用它的频率,或者成功或失败的可能性更大。只有调用者有此信息。试图避免异常会导致不清楚的 API 习惯,在某些情况下,我们只有干净但缓慢的异常版本,而在其他情况下,我们会遇到快速但笨拙的返回值错误,而在其他情况下,我们最终会同时出现这两种情况.库实现者可能必须编写和维护两个版本的 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
        

        检查和传播返回值确实会增加一些与baseline-null 调用相比的成本,并且该成本与调用深度成正比。在调用链深度为 8 时,错误返回值检查版本比不检查返回值的基线版本慢约 27%。

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

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

        【讨论】:

          【解决方案11】:

          HotSpot 非常有能力删除系统生成的异常的异常代码,只要它们都是内联的。但是,显式创建的异常和那些没有被删除的异常会花费大量时间来创建堆栈跟踪。覆盖 fillInStackTrace 以查看这会如何影响性能。

          【讨论】:

            【解决方案12】:

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

            我想这并不能真正回答这个问题。我想在早期的java版本(

            【讨论】:

            • 最好定义“正常程序流程”。已经有很多关于使用检查异常作为业务流程失败和使用未经检查异常作为不可恢复故障的文章,因此从某种意义上说,业务逻辑失败仍然可以被认为是正常流程。
            • @Spencer K:异常,顾名思义,意味着发现了异常情况(文件消失,网络突然关闭,......)。这意味着情况出乎意料。如果预计会发生这种情况,我不会为此使用异常。
            • @Mecki:对。我最近与某人讨论过这个问题......他们正在编写一个验证框架,并在验证失败的情况下抛出异常。我认为这是一个坏主意,因为这很常见。我宁愿看到该方法返回一个 ValidationResult。
            • 就控制流而言,异常类似于breakreturn,而不是goto
            • 有大量的编程范式。不可能有一个单一的“正常流程”,不管你是什么意思。基本上,异常机制只是一种快速离开当前帧并展开堆栈直到某一点的方法。 “例外”这个词并不意味着它的“意外”性质。一个简单的例子:当某些情况发生在路由路径上时,从 Web 应用程序中“抛出”404 是很自然的。为什么不使用异常来实现该逻辑?什么是反模式?
            【解决方案13】:

            只需将我们说的 Integer.parseInt 与以下方法进行比较,该方法仅在无法解析数据的情况下返回默认值,而不是抛出异常:

              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 次),性能上的差异应该是必不可少的。

            【讨论】:

              【解决方案14】:

              使用附带的代码,在 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);
                  }
                }
              }
              
              

              【讨论】:

              • 但这有什么关系呢?我的基准不是为了证明异常很慢,它只是为了测试它们是否慢。我只是将我的结果作为示例输出分享,而不是为了证明任何观点。它们现在可能以不同的方式实现,好吧,正如我所解释的,您可以通过多种方式实现它们。这如何伪造我在答案中写的任何内容?当我说这取决于时,我的答案的第一句话的哪一部分不清楚?它还取决于系统,所有系统的 JVM 代码并不相同,我所说的只是“不要相信它们很快”。所以我想念你的意思
              • 我来这里是为了寻找问题的答案。当我发现当前答案中的某些内容在最近的硬件/版本上不再准确时,我倾向于发表评论,以便其他人可以更好地了解情况。
              【解决方案15】:

              关于异常性能的好帖子是:

              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-shit 由于异常 (xD) 而运行缓慢。

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

              【讨论】:

              • 这篇文章很棒,但是您关于 Hibernate/Spring/EE 由于异常而变慢的结论并不是基于您在此处提供的任何内容。如果您的 Hibernate/Spring 应用程序的 CPU 已用尽,那么它可能就是这样。更有可能的是,但它是另一回事。性能不佳的其他原因是完全不了解 Hibernate 的底层功能,并且使用 ORM 并不意味着如果不仔细检查它正在执行的 SQL 语句(以及有多少)不是非常低效就可以获得良好的性能.
              【解决方案16】:

              我在上面更改了@Mecki 的答案,让 method1 返回一个布尔值并检查调用方法,因为您不能只用任何内容替换 Exception 。两次运行后,method1 仍然是最快的,或者与 method2 一样快。

              代码截图如下:

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

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

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

                通常有形式

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

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

                有时存在第二种形式:

                public static Example Parse(String input, Example defaultValue)
                

                从不投掷

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

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

                有了这个代码,程序员就没有异常成本。但是,正则表达式的成本非常高,有时异常的成本很小。

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

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

                没有分析stacktrace等,我相信在你的讲座之后相当快。

                不要害怕例外

                【讨论】:

                  【解决方案18】:

                  为什么异常应该比正常返回慢?

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

                  好问题,我期待有关此主题的更多信息!

                  【讨论】:

                  • 异常必须捕获​​有关堆栈跟踪的信息,即使它实际上并没有被使用。
                  猜你喜欢
                  • 2015-12-03
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-09-22
                  • 1970-01-01
                  • 2013-09-06
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-01-17
                  相关资源
                  最近更新 更多