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

Bob*_*ous 60 java performance null

从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
}
Run Code Online (Sandbox Code Playgroud)

我认为这种方法可以使代码非常整齐,易于阅读,我正在努力鼓励同事使用它.但是一位(特别是知识渊博的)同事很有耐心,并说老方法更有效率:

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
}
Run Code Online (Sandbox Code Playgroud)

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

所以我的问题是:有没有证据表明使用这些Objects.requireNonNull方法会导致性能损失?

T.J*_*der 72

让我们看一下requireNonNullOracle JDK 中的实现:

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}
Run Code Online (Sandbox Code Playgroud)

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

所以不,不可能更慢,不是以任何有意义的方式,而不是任何重要的地方.

所以我的问题是:有没有证据表明使用这些Objects.requireNonNull方法会导致性能损失?

这将事情的唯一证据是性能测量你的代码库,或代码设计是极具代表性的吧.您可以使用任何不错的性能测试工具对此进行测试,但除非您的同事可以指出与此方法相关的代码库中的性能问题的真实示例(而不是合成基准),我倾向于假设您和他/她有更大的鱼来炒.


稍微提一下,我注意到你的样本方法是一种private方法.因此,只有您的团队正在编写代码直接调用它.在这些情况下,您可能会查看是否有用于断言的用例而不是运行时检查.断言具有不在"已发布"代码中执行的优点,因此比您的问题中的任何一个更快.显然,有些地方需要运行时检查,但这些通常是在守门点,公共方法等.只是FWIW.

  • @Bobulous:字符串已经存在,因为在加载类文件并将其放入字符串池时预先加载了字符串文字.如果您正在执行`Objects.requireIfNull(blah,"literal"+ runtimeString);`,那么您将在运行时(在封面下)创建一个`StringBuilder`来进行连接,即使结果字符串不是用过的.这可能是一些开销.但是,如果您使用文字或对现有静态字符串的引用,则不会.**并且**永远不要低估JIT!它完全不允许内联,但*重新排序*,代码以避免这样的死店. (7认同)
  • @Bobulous这个答案是完全正确的,让我补充一下,如果您需要动态消息,请考虑使用 Guava 的 [checkNotNull 和 errorMessageTemplate](http://docs.guava-libraries.googlecode.com/git/javadoc/ com/google/common/base/Preconditions.html#checkNotNull(T,%20java.lang.String,%20java.lang.Object...))。如果不需要的话,这会消除字符串连接(而是创建一个可变参数数组,这更便宜并且可能更容易被 JIT 消除)。 (2认同)

lev*_*tov 20

从形式上讲,你的同事是对的:

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

  • 如果someMethod()从热点调用9级深度,requireNonNull()则不应该因为JVM选项而内联调用MaxInlineLevel

  • 如果由于上述任何原因没有内联方法,那么如果使用串联来生成错误消息,则TJ Crowder的参数会起作用

  • 即使requireNonNull()内联,JVM也会浪费时间和空间来执行此操作.

另一方面,有FreqInlineSizeJVM选项,它禁止内联太大(字节码)方法.方法的字节码由它们自己计算,没有在此方法中调用的方法的会计大小.因此,有时候将代码片段提取到独立的方法中可能很有用,在这个例子中,requireNonNull()已经为你提取了这个提取.


Ste*_*n C 5

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

(我建议先看看Calliper项目!或JMH ......按照Boris的建议.无论哪种方式,都不要尝试从头开始编写微基准测试.有太多方法可以解决它.)

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

  • JIT编译器可以很好地内联小方法调用,在这种情况下很可能会发生这种情况.

  • 如果它没有内联呼叫,那么性能差异可能只是3到5条指令,并且它不太可能产生显着差异.

  • 这个[空检查方法的JMH比较](http://cr.openjdk.java.net/~shade/scratch/NullChecks.java)表明即使在内联失败的情况下,`Objects.requireNonNull`方法也需要少于比if-null代码长2ns.代码内联的地方,两种方式之间似乎没有任何显着差异. (3认同)