'... != null' 或 'null != ...' 哪个性能更好?(涉及IT技术)

53

我写了两个方法来检查它们的性能。

 public class Test1 {

 private String value;

 public void notNull(){
  if( value != null) {
    //do something
  }
}

public void nullNot(){
 if( null != value) {
  //do something
 }
}

}

我检查了它编译后的字节码

public void notNull();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: getfield #2; //Field value:Ljava/lang/String;
4: ifnull 7
7: return
LineNumberTable: 
line 6: 0
line 9: 7

StackMapTable: number_of_entries = 1
frame_type = 7 /* same */


public void nullNot();
Code:
Stack=2, Locals=1, Args_size=1
0: aconst_null
1: aload_0
2: getfield #2; //Field value:Ljava/lang/String;
5: if_acmpeq 8
8: return
LineNumberTable: 
line 12: 0
line 15: 8

StackMapTable: number_of_entries = 1
frame_type = 8 /* same */


}

这里使用了两个操作码来实现if条件语句:第一种情况使用ifnull——检查堆栈顶部的值是否为null-,第二种情况使用if_acmpeq——检查堆栈中的前两个值是否相等。

所以,这会对性能产生影响吗? (这将帮助我证明第一种空值实现在性能和可读性方面都很好 :))


4
Polygene的回答是你应该听取的。但如果你真的认为这种差异很重要,那就分别运行每个版本数十亿次,看看是否有任何可测量的差异。然后回来告诉我们结果。 - Peter Recore
为什么这很重要,你的程序只有三行长,以至于那个孤独的if语句运行得快慢如此重要吗? - mP.
7
顺便说一下,我想证明将开括号放在条件语句同一行比其他大括号风格更具性能优势。 - Ben Zotto
18
我会说OP需要得到表扬,因为他付出了努力,深入研究了字节码等。在得到适当的指导后,这种决心将对OP有好处。 - polygenelubricants
16个回答

82

比较生成的字节码大多没有意义,因为大多数优化是在运行时使用JIT编译器完成的。我猜在这种情况下,两个表达式速度相等。如果有任何差异,那么可以忽略不计。

这不是你需要担心的事情。寻找大局优化。


7
+1 表示区分字节码和汇编语言是非常重要的,需要特别注意。 - Cam
5
其实很简单:如果其中一个比另一个更快,微软的一些聪明人早就会让编译器或JIT将慢的转换成快的了。 - Nicolás
字节码方面的观点很好,但是你怎么知道这不是一个大局优化呢?如果你正在查看稀疏数据,检查 null 可能是代码花费大部分时间的地方。 - Rex Kerr
29
@Nicolas,考虑到这是Java而不是C#,微软的人可能会让编译器将快速的转换成慢速的 :) - Peter Recore
唉,又要在“我有注意力缺陷多动症的证据清单”上添加一项了。 - Nicolás
1
事实上,经过这么多年后,人们仍然经常忽略更大的画面,这是非常令人沮丧的...... 很适当地回答了问题,即使到现在仍然相关。 - Soumen Mukherjee

23

如果以可读性为代价进行优化,而速度(或内存/其他情况)的增益可以忽略不计,则不要牺牲可读性。 我认为使用!=null通常更易读,因此请使用它。


我同意你关于可读性的观点。null是常量,对于包含常量的比较,variable <comparison> constant 是最易读的。 - Ponkadoodle

12

像这样的问题,很难知道JVM有多聪明(尽管答案通常是“如果可能的话通常相当聪明”,在这种情况下看起来非常可能)。但为了确保,测试一下:

class Nullcheck {
  public static class Fooble { }

  Fooble[] foo = {null , new Fooble(), null , null,
                  new Fooble(), null, null, new Fooble() };

  public int testFirst() {
    int sum = 0;
    for (int i=0 ; i<1000000000 ; i++) if (foo[i&0x7] != null) sum++;
    return sum;
  }

  public int testSecond() {
    int sum = 0;
    for (int i=0 ; i<1000000000 ; i++) if (null != foo[i&0x7]) sum++;
    return sum;
  }

  public void run() {
    long t0 = System.nanoTime();
    int s1 = testFirst();
    long t1 = System.nanoTime();
    int s2 = testSecond();
    long t2 = System.nanoTime();
    System.out.printf("Difference=%d; %.3f vs. %.3f ns/loop (diff=%.3f)\n",
      s2-s1,(t1-t0)*1e-9,(t2-t1)*1e-9,(t0+t2-2*t1)*1e-9);
  }

  public static void main(String[] args) {
    Nullcheck me = new Nullcheck();
    for (int i=0 ; i<5 ; i++) me.run();
  }
}

在我的机器上运行此代码会得到以下结果:

Difference=0; 2.574 vs. 2.583 ns/loop (diff=0.008)
Difference=0; 2.574 vs. 2.573 ns/loop (diff=-0.001)
Difference=0; 1.584 vs. 1.582 ns/loop (diff=-0.003)
Difference=0; 1.582 vs. 1.584 ns/loop (diff=0.002)
Difference=0; 1.582 vs. 1.582 ns/loop (diff=0.000)

因此答案是:没有任何实质性的区别。 (并且即时编译器可以在相同次数的重复运行后找到额外的技巧来加速每个操作。)
更新:上面的代码运行了一个临时基准测试。使用JMH(现在已经存在!)是避免(某些)微基准测试陷阱的好方法。上面的代码避免了最糟糕的陷阱,但它没有给出明确的误差估计,并忽略了其他一些有时很重要的事情。现在:使用JMH!此外,如果有疑问,请运行自己的基准测试。细节有时很重要 - 对于像这样简单的东西来说并不经常发生,但如果对您非常重要,您应该检查尽可能接近生产条件的情况。

7
除了避免在C语言中意外赋值的经验之外,倾向于将常量放在二元运算符左侧,我发现将常量放在左侧更易读,因为它将关键值放在最显著的位置。
通常,函数体只会使用几个变量,并且通常可以通过上下文明显地知道正在检查哪个变量。通过将常量放在左侧,我们更接近于switch和case:给定这个变量,选择匹配的值。看到左侧的值,人们会专注于所选条件。
当我扫描时
if (var == null)

我理解为,这里我们正在检查var,并将其与null进行比较。相反地,当我扫描时...
if (null == var)

我认为,我们正在判断一个值是否为空,而且...是的,我们正在检查var。这样做会更加明确。

if (null != var)

我眼中立刻就能看出来。

这种直觉来自于习惯的一致性,喜欢读自己写的东西,也喜欢写自己喜欢读的东西。可以从任何一种方式学习,但并不是客观事实,因为其他人在这里回答说将变量放在左边更清晰。这取决于表达式中想要最清晰的方面。

看到字节码的差异非常有趣。谢谢分享。


17
各人有各人的直觉...(虽然你关于这个肯定是错的。;)) - Ben Zotto
6
“避免在 C 语言中发生意外赋值的经验”已经过时约 20 年,因为 C 编译器现在会产生有关此类警告(而不是从“lint”获取),而且它实际上并不适用于 Java。 - user207421
编译器现在对此发出警告并不改变长期程序员可能最初采用这种风格的原因,但这并不重要。我在这里的观点是避免这种意外赋值并不是这种风格的唯一好处,今天仍有充分的理由采用它。根据读者最感兴趣的内容,这种风格可以“读起来更好”。 - seh

3
差别微不足道,所以选择最易读的方式(我认为是 != null)。

2

为了可读性,我建议使用 (value != null)。但是你也可以使用断言(Assertions)。


2

像这样的微小优化是编译器的工作,特别是在高级语言(如Java)中。

尽管这不严格相关,在进行优化之前不要过早优化!


2
您IP地址为146.190.162.183,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。
if (obj == null)

可能会被错误地写成:

if (obj = null)

从编译器的角度来看,这是可以的。

然而,如果您习惯于将代码编写为:

if (null == obj)

并犯了一个错误,写成了:

if (null = obj)

编译器会提示你在那一行犯了错误。

2
这个问题已经被讨论过了,请参考另一个答案的评论。你应该编辑这个答案,解释为什么你认为它有用,即使那些评论者认为它没有用。 - Peter Cordes

1

null放在第一位似乎会生成额外的字节码,但除此之外可能没有性能差异。

个人而言,在需要担心性能之前,我不会担心性能。

我会使用notNull()方法,这样如果您忘记了!并意外输入null = value,就不会抛出编译器错误。


但是在 if 条件语句中,如果你犯了同样的错误,它将无法编译,如果不是布尔类型的值。 - asela38
1
我认为在输入notNull(value)时出现打字错误的可能性比value != null要大。 - Ponkadoodle
是的,我同意。我本意是在进行类型检查时要讲究普遍性,但我没有表达清楚。谢谢。 - Anthony Forloney

1

哦,如果你要求终极性能,请不要创建额外的类或方法。即使是静态方法也需要一点时间,因为Java类加载器需要JIT加载它。

所以,每当你需要检查一个变量是否为空时,只需通过以下方式测试:

if (x == null)

或者

if (null == x)

坦白地说,我认为选择其中一个的绩效奖金很容易被引入不必要方法的开销所抵消。

我相信这些方法只是为了方便查看生成的字节码而被引入的。 - Christian Semrau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接