【问题标题】:' ... != null' or 'null != ....' best performance?' ... != null' 还是 'null != ....' 最好的表现?
【发布时间】:2011-01-24 19:26:47
【问题描述】:

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

 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-检查堆栈中的顶部两个值是否相等-

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

【问题讨论】:

  • polygene 的回答是你应该听的。但是,如果您真的认为差异很重要,请将每个版本运行十亿次左右,看看是否有任何可测量的差异。然后回来告诉我们。
  • 为什么重要,你的程序是否只有 3 行长,重要的是一个单独的 if 语句运行多快或多慢?
  • 当我们谈到这个主题时,我想证明将左大括号放在与条件相同的行上比其他大括号样式更有效。
  • 我会说OP需要被称赞为努力,挖掘字节码等。如果方向正确,这种决心对OP来说会很好。

标签: java performance micro-optimization


【解决方案1】:

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

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

【讨论】:

  • +1 用于区分字节码和汇编——这是一个非常重要的区别。
  • 这真的很简单:如果一个比另一个快,微软的一些聪明人会已经让编译器或 JIT 将慢的转换成快的.
  • 关于字节码的好点,但你怎么知道这不是大局优化?如果您正在查看人口稀少的数据,检查 null 可能是您的代码花费大部分时间的地方。
  • @Nicolas,考虑到这是 java,而不是 c#,微软的人可能会让编译器将快编译器转换为慢编译器 :)
  • 呃。将另一个项目添加到“我患有多动症的证据”列表中。
【解决方案2】:

首先放置null 似乎会生成一个额外的字节码,但除此之外可能没有性能差异。

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

我会使用notNull() 方法,这样您就不会在忘记! 并意外键入null = value 时引发编译器错误。

【讨论】:

  • 但是在if条件下如果你犯了同样的错误,它不会编译,if not value是布尔类型
  • 我认为您在输入notNull(value) 时比value != null 更有可能打错字。
  • 是的,同意,我的意思是在执行类型检查时泛泛而谈,但我没有说清楚。谢谢。
【解决方案3】:

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

【讨论】:

  • 我同意你所说的可读性。 null 是常量,对于包含常量 variable <comparison> constant 的比较而言,可读性最强。
【解决方案4】:

差异可以忽略不计,所以选择最易读的 (!= nullimo)

【讨论】:

    【解决方案5】:

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

    所以,每当您需要检查一个变量是否为空时,您只需通过以下任一方式对其进行测试

    if (x == null)
    

    if (null == x)
    

    坦率地说,我认为选择两者之一的性能奖励很容易被引入不必要的方法的开销所抵消。

    【讨论】:

    • 我确信这些方法只是作为查看生成字节码差异的简单方法而引入的。
    【解决方案6】:

    为了便于阅读,我会坚持使用 (value != null)。但您始终可以使用断言。

    【讨论】:

      【解决方案7】:

      除了避免在 C 中意外赋值的来之不易的智慧(倾向于将常量放在二元运算符的左侧)之外,我发现左侧的常量更具可读性,因为它将关键价值放在最突出的位置。

      通常一个函数体将只使用几个变量,并且通常通过上下文可以明显看出哪个变量正在检查中。通过把常量放在左边,我们更接近地模仿switchcase:给定this 变量,选择一个匹配的值。看到左侧的值,可以将注意力集中在选择的特定条件上。

      当我扫描时

      if (var == null)
      

      我把它读作“我们在这里检查var,我们正在比较它的相等性,与......啊,null。”相反,当我扫描时

      if (null == var)
      

      我认为,“我们正在查看一个值是否为空,并且……是的,我们正在检查的是 var。”

      if (null != var)
      

      我的眼睛立刻就注意到了。

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

      看到字节码的差异令人着迷。感谢分享。

      【讨论】:

      • 每个人都有自己的直觉......(虽然你肯定错了。;))
      • “避免在 C 中意外赋值的来之不易的智慧”已经过时了大约 20 年,因为 C 编译器现在会对此产生警告(而不是必须从“lint”中获取它们),并且它实际上并不适用于 Java。
      • 编译器现在对此发出警告并不会改变长期程序员可能首先采用这种风格的原因,但这并不重要。我的观点是,避免这种意外分配并不是这种风格的唯一好处,今天仍然有充分的理由采用它。根据读者最感兴趣的内容,这种风格可以“更好地阅读”。
      【解决方案8】:

      像这样的微小优化是编译器的工作,尤其是在 Java 这样的高级语言中。

      虽然严格来说这里不相关,但不要过早优化!

      【讨论】:

        【解决方案9】:

        对于这样的问题,很难知道 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)
        

        所以答案是:不,根本没有有意义的区别。 (并且 JIT 编译器可以在相同数量的重复运行后找到额外的技巧来加快速度。)


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

        【讨论】:

          【解决方案10】:

          您可以在编码过程中忽略这些非常细微的优化内容

          【讨论】:

            【解决方案11】:

            如您所见,性能差异非常小。不要担心小事情,最好更多地关注算法。显然,可读性是一个因素。

            【讨论】:

              【解决方案12】:

              字节码只是源代码的简单翻译。

              【讨论】:

              • 这完全取决于您使用的编译器。
              【解决方案13】:

              从性能上看,没有显着差异。

              但是,先写入 null 以捕获拼写错误很有用。

              例如,如果你习惯写这段代码:

              if (obj == null)
              

              可能误写为:

              if (obj = null)
              

              从编译器的角度来看,这很好。

              但是,如果你习惯将代码写成:

              if (null == obj)
              

              写错了:

              if (null = obj)
              

              编译器会让你知道你在那一行犯了一个错误。

              【讨论】:

              • 这已经讨论过了,见comments on another answer。您应该编辑此答案以解释为什么您认为这很有用,即使那些评论者说它不是。
              【解决方案14】:

              在 Java-8 中,Objects 类引入了两个额外的方法: Objects#nonNullObjects#isNull,可用于替换 null 检查。一个有趣的事情是它们都先使用对象:

              public static boolean isNull(Object obj) {
                  return obj == null;
              }
              

              public static boolean nonNull(Object obj) {
                  return obj != null;
              }
              

              相应地。我想这意味着这是推荐的方式(至少核心 jdk 开发人员使用了这种方式) Objects source code

              【讨论】:

                【解决方案15】:

                我会使用 Java 8 的“新”特性,我写了几个例子:

                import java.util.Optional;
                
                public class SillyExample {
                
                public void processWithValidation(final String sampleStringParameter){
                    final String sampleString = Optional.ofNullable(sampleStringParameter).orElseThrow(() -> new IllegalArgumentException("String must not be null"));
                
                    //Do what you want with sampleString
                }
                
                
                public void processIfPressent(final String sampleStringParameter){
                    Optional.ofNullable(sampleStringParameter).ifPresent(sampleString -> {
                
                        //Do what you want with sampleString
                
                    });
                
                }
                
                public void processIfPressentWithFilter(final String sampleStringParameter){
                    Optional.ofNullable(sampleStringParameter).filter("hello"::equalsIgnoreCase).ifPresent(sampleString -> {
                
                        //Do what you want with sampleString
                
                    });
                
                }
                

                }

                【讨论】:

                  【解决方案16】:

                  我更喜欢null != object,因为它清楚地表明它只是用于空检查。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2011-05-21
                    • 1970-01-01
                    • 2012-11-12
                    • 2016-01-03
                    • 1970-01-01
                    • 2021-03-21
                    • 2011-12-06
                    • 1970-01-01
                    相关资源
                    最近更新 更多