'...!= null'或'null!= ....'最佳表现?

ase*_*a38 41 java performance micro-optimization

我写了两种方法来检查性能

 public class Test1 {

 private String value;

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

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

}
Run Code Online (Sandbox Code Playgroud)

并在编译后检查它的字节码

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


}
Run Code Online (Sandbox Code Playgroud)

在这里,两个操作码用于实现if条件:在第一种情况下,它使用ifnull-检查堆栈的最高值为null-,在第二种情况下,它使用if_acmpeq-检查前两个值在堆栈中是相等的 -

那么,这会对性能产生影响吗?(这将有助于我证明null的首次实现在性能方面以及在可读性方面都很好:))

pol*_*nts 76

比较生成的字节码大多没有意义,因为大多数优化都是在运行时使用JIT编译器进行的.我猜想在这种情况下,表达式同样快.如果有任何差异,则可以忽略不计.

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

  • @Nicolas,考虑到这是java,而不是c#,微软的人可能会让编译器将快速的转换为慢速的:) (26认同)
  • 用于区分字节码和汇编的+1 - 这是一个非常重要的区别. (6认同)
  • 这真的非常简单:如果一个比另一个快,那么微软的一些聪明人会*已经*使编译器或JIT将慢速转换为快速编译器. (5认同)

Cam*_*Cam 21

如果速度(或存储器/无论情况如何)增益可以忽略不计,则不以牺牲可读性为代价进行优化.我认为!=null通常更具可读性,所以使用它.


Rex*_*err 10

有了这样的问题,很难知道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();
  }
}
Run Code Online (Sandbox Code Playgroud)

在我的机器上产生:

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

所以答案是:不,没有任何有意义的差异.(并且JIT编译器可以在相同次数的重复运行之后找到额外的技巧来加速每个.)


更新:上面的代码运行ad-hoc基准测试.使用JMH(现在它存在!)是帮助避免(某些)微基准测试陷阱的好方法.上面的代码避免了最严重的陷阱,但它没有给出明确的错误估计,而忽略了有时重要的其他各种事情.这些天:使用JMH!此外,如有疑问,请运行自己的基准测试.细节有时很重要 - 对于像这样简单的事情来说并不常见,但如果对你来说非常重要,你应该检查一个尽可能接近生产的条件.


seh*_*seh 7

除了在C中避免意外赋值的来之不易的智慧,它有利于将常量放在二元运算符的左边,我发现左边的常量更具可读性,因为它将关键值放在最显着的位置.

通常一个函数体只会使用几个变量,而且通常通过上下文显而易见的是哪个变量正在检查中.通过将常量放在左侧,我们更接近地模仿switch并且case:给定变量,选择匹配值.看到左侧的值,一个侧重于所选的特定条件.

当我扫描

if (var == null)
Run Code Online (Sandbox Code Playgroud)

我读到它,"我们正在var这里检查,我们正在比较它是否平等,反对......啊,null." 相反,当我扫描

if (null == var)
Run Code Online (Sandbox Code Playgroud)

我想,"我们看到一个值是否为空,并且......是的,var我们正在检查它." 这是一个更强烈的认可

if (null != var)
Run Code Online (Sandbox Code Playgroud)

我的眼睛立即接受了.

这种直觉来自于习惯的一致性,更喜欢阅读所写的内容,以及写出喜欢阅读的内容.人们可以从任何一种方式学习它,但它不是客观真实的,因为其他人在这里回答说将变量放在左边是更清楚的.这取决于首先想要最明确的表达方式.

看到字节码差异很有吸引力.谢谢你的分享.

  • 对每一个他们自己的直觉......(虽然你肯定是错的.;)) (17认同)
  • "避免在C中意外分配的难以获得的智慧"已经过时了大约20年,因为C编译器现在会对此产生警告(而不是必须从'lint'获取它们),并且它实际上并不适用于Java . (5认同)