Objects.requireNonNull 比旧方法效率低吗?

Is Objects.requireNonNull less efficient than the old way?

自 JDK 7 以来,我一直很高兴地使用它引入的方法来拒绝传递给无法接受它们的方法的 null 值:

private void someMethod(SomeType pointer, SomeType anotherPointer) {
    Objects.requireNonNull(pointer, "pointer cannot be null!");
    Objects.requireNonNull(anotherPointer, "anotherPointer cannot be null!");
    // Rest of method
}

我认为这种方法使代码非常整洁,易于阅读,我正在努力鼓励同事使用它。但是一位(特别有见识的)同事反对,并说旧方法更有效:

private void someMethod(SomeType pointer, SomeType anotherPointer) {
    if (pointer == null) {
        throw new NullPointerException("pointer cannot be null!");
    }
    if (anotherPointer == null) {
        throw new NullPointerException("anotherPointer cannot be null!");
    }
    // Rest of method
}

他说调用 requireNonNull 涉及在 JVM 调用堆栈上放置另一个方法,并且会导致比简单的 == null 检查更差的性能。

所以我的问题是:是否有任何 证据表明 使用 Objects.requireNonNull 方法会导致性能下降?

你的同事很可能是错的。

JVM 非常智能,很可能会内联 Objects.requireNonNull(...) 方法。性能值得怀疑,但肯定会有比这更严重的优化。

您应该使用 JDK 中的实用方法。

我们来看requireNonNull在Oracle的JDK中的实现:

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

所以这非常简单。 JVM(无论如何是 Oracle 的)包括一个优化的两阶段即时编译器,用于将字节码转换为机器码。如果这样可以获得更好的性能,它将像这样内联琐碎的方法。

所以不,不太可能变慢,没有任何有意义的方式,没有任何重要的地方。

So my question: is there any evidence of a performance penalty being incurred by using the Objects.requireNonNull methods?

唯一重要的证据是您的代码库的性能测量,或者设计为高度代表它的代码的性能测量。您 可以 使用任何合适的性能测试工具对此进行测试,但除非您的同事可以指出您的代码库中与此方法相关的性能问题的真实示例(而不是综合基准) ), 我倾向于假设你和 he/she 有更大的鱼要炸。


顺便说一句,我注意到您的示例方法是 private 方法。因此,只有您的团队正在编写的代码会直接调用它。在这些情况下,您可能会查看是否有 assertions 的用例,而不是运行时检查。断言的优点是根本不在 "released" 代码中执行,因此比您的问题中的任何一个选项都快。显然有些地方需要运行时检查,但通常是在看门点、public 方法等地方。只是 FWIW。

如果你想要证据 ...那么得到它的方法就是写一个微基准。

(我建议先看看 Calliper 项目!或 JMH ... 根据 Boris 的建议。无论哪种方式,都不要尝试从头开始编写微基准测试。有太多方法会出错。 )

但是,您可以告诉您的同事两件事:

  • JIT 编译器在内联小方法调用方面做得很好,在这种情况下很可能会发生这种情况。

  • 如果它没有内联调用,则性能差异很可能只是 3 到 5 条指令,而且不太可能产生显着差异。

正式地说,你的同事是对的:

  • 如果someMethod()或相应的跟踪不够热,解释字节码,并创建额外的堆栈帧

  • 如果 someMethod() 在热点的第 9 层深度调用,则不应内联 requireNonNull() 调用,因为 MaxInlineLevel JVM Option

  • 如果由于上述任何原因未内联该方法,则由 T.J 论证。 Crowder 开始发挥作用,如果您使用串联来产生错误消息

  • 即使 requireNonNull() 是内联的,JVM 也会浪费时间 space 执行此操作。

另一方面,有 FreqInlineSize JVM 选项,它禁止内联太大(字节码)的方法。该方法的字节码是自己计算的,不考虑方法的大小,在该方法内调用。因此,有时将代码片段提取到独立的方法中可能很有用,在 requireNonNull() 的示例中,此提取已经为您完成。

Objects.requireNonNull 更优化,就好像您是代码可重用性一样。 同样在 oracle 中 requireNonNull si 定义为

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
} 

所以它已经在字节码中了。

作为一般规则,可读性和可维护性应该胜过优化。

此规则可防止那些自认为了解编译器工作原理的人进行推测性优化,即使他们从未尝试编写编译器,也从未深入了解编译器。

你的同事是错误的,除非他们证明性能损失对用户来说是明显的和站不住脚的。

有效 Java 作者:约书亚·布洛赫

Item 67: Optimize judiciously

There are three aphorisms concerning optimization that everyone should know:

More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason—including blind stupidity.
—William A. Wulf [Wulf72]

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
—Donald E. Knuth [Knuth74]

We follow two rules in the matter of optimization:
Rule 1. Don’t do it.
Rule 2 (for experts only). Don’t do it yet—that is, not until you have a perfectly clear and unoptimized solution.
—M. A. Jackson [Jackson75]

是的,有证据表明手动 null 检查和 Objects.requireNonNull() 之间的差异可以忽略不计。 OpenJDK 提交者 Aleksey Shipilev 创建了 benchmarking code that proves this while fixing JDK-8073479,这是他的结论和性能数据:

TL;DR: Fear not, my little friends, use Objects.requireNonNull.
       Stop using these obfuscating Object.getClass() checks,
       those rely on non-related intrinsic performance, potentially
       not available everywhere.

Runs are done on i5-4210U, 1.7 GHz, Linux x86_64, JDK 8u40 EA.

The explanations are derived from studying the generated code
("-prof perfasm" is your friend here), the disassembly is skipped
for brevity.

Out of box, C2 compiled:

Benchmark                  Mode  Cnt  Score   Error  Units
NullChecks.branch          avgt   25  0.588 ± 0.015  ns/op
NullChecks.objectGetClass  avgt   25  0.594 ± 0.009  ns/op
NullChecks.objectsNonNull  avgt   25  0.598 ± 0.014  ns/op

Object.getClass() is intrinsified.
Objects.requireNonNull is perfectly inlined.

其中branchobjectGetClassobjectsNonNull定义如下:

@Benchmark
public void objectGetClass() {
    o.getClass();
}

@Benchmark
public void objectsNonNull() {
    Objects.requireNonNull(o);
}

@Benchmark
public void branch() {
    if (o == null)  {
        throw new NullPointerException();
    }
}

嗯,不。但是,是的。

,直接代码总是更好,因为不需要触及方法堆栈。

,如果 VM 实现有空检查跳过或一些优化的空检查。

,方法栈太轻了修改更新(不过还是要耗费时间)