【问题标题】:Is there any performance difference between a value comparison and a value assignment? [closed]值比较和值分配之间是否存在性能差异? [关闭]
【发布时间】:2013-01-06 04:52:17
【问题描述】:

考虑以下两个代码示例:

示例 1.

public void setValue(int value)
{
    mValue = value;
}

示例 2。

public void setValue(int value)
{
    if (mValue != value)
    {
        mValue = value;
    }
}

假设您的任务是优化一些 Java 代码到绝对最大值,超出所有常识。

第二个代码示例会是第一个代码示例的优化吗?我的意思是,在 Java 或 JVM 的最低级别的 if 条件检查和 int 赋值之间有什么区别(无论多么微小)?

【问题讨论】:

  • “超出所有常识” - 确实。
  • 收益部分取决于您将相同值分配给mValue 的频率。如果这种情况很少发生,那么比较是不必要的开销。

标签: java performance optimization jvm atomic


【解决方案1】:

第二个可能是反优化,因为在现代处理器上分支往往很昂贵(主要是因为branch misprediction 非常昂贵)。

确定答案的唯一方法是根据代表性输入分析您的代码。请参阅How do I write a correct micro-benchmark in Java?,了解有关如何设置有意义的微基准的一些提示。

请记住,如果有问题的代码不占整个 CPU 时间的很大一部分,那么世界上的所有优化都不会对您的应用程序的整体性能产生很大影响。

【讨论】:

  • 如果你很幸运,条件分支将很容易预测,并且不会比典型操作更昂贵。如果你不走运,它将不得不在它执行的大部分时间里刷新管道。
  • @yshavit 下面提出的观点适用于所有并发访问,无论该字段是否易失。您正在用一个分支来换取一个可能毫无意义的写入缺失,这对于给定的字段更改概率来说可能是一个很好的交易。
【解决方案2】:

NPE 有很好的建议。我要补充的一条评论是,这可能还取决于mValue 是否易失,以及您在什么硬件上运行它。对于易失性,写入可能比读取昂贵几倍。在某些硬件(例如我相信许多移动设备)中,易失性读取远非便宜,因此这种差异可能会缩小。

【讨论】:

    【解决方案3】:

    您可以尝试编译两者,然后使用 java 字节码反汇编程序来检查差异。 Wikipedia JVM instruction listings 可能是一个不错的起点。

    根据我对使用过的编译器和 VM 的了解,由于额外的比较操作,第二个代码 sn-p 可能会(微不足道)慢一点。

    如果你的 JVM 实现有一个优化器,它可能能够将你的第一个代码示例减少到一个操作,而第二个 sn-p 将需要至少一个或两个更多的操作来解释比较操作。

    【讨论】:

      【解决方案4】:

      良好的编码实践说:“在最后一刻之前延迟优化”

      但是代码测试说:

      • TOTAL(1)=1000000000 1420ms TOTAL(2)=1000000000 2155ms
      • TOTAL(1)=1000000000 1374ms TOTAL(2)=1000000000 1884ms
      • TOTAL(1)=1000000000 1379ms TOTAL(2)=1000000000 2072ms

      比较和设置并不好。

      // Test Code
      public class CompareAndSet {
          int mValue;
      
          // 1
          public void setValue1(int value) {
              mValue = value;
          }
      
          // 2
          public void setValue2(int value) {
              if (mValue != value) {
                  mValue = value;
              }
          }
      
          public static void main(String[] args) {
              final int TOTAL = (int) 1e9;
              long ts;
              for (int j = 0; j < 3; j++) {
                  // 1
                  {
                      ts = System.currentTimeMillis();
                      CompareAndSet cs = new CompareAndSet();
                      for (int i = 0; i < TOTAL; i++) {
                          cs.setValue1(i);
                      }
                      System.out.println("TOTAL(1)=" + TOTAL + " "
                              + (System.currentTimeMillis() - ts) + "ms");
                  }
                  // 2
                  {
                      ts = System.currentTimeMillis();
                      CompareAndSet cs = new CompareAndSet();
                      for (int i = 0; i < TOTAL; i++) {
                          cs.setValue2(i);
                      }
                      System.out.println("TOTAL(2)=" + TOTAL + " "
                              + (System.currentTimeMillis() - ts) + "ms");
                  }
              }
          }
      
      }
      

      【讨论】:

      • 如果将“setValueX(i)”更改为 setValueX(1),则时间为(比较和设置总是不好): - TOTAL(1)=1000000000 1390ms TOTAL(2)=1000000000 1386ms - TOTAL (1)=1000000000 1354ms TOTAL(2)=1000000000 1353ms - TOTAL(1)=1000000000 1398ms TOTAL(2)=1000000000 1389ms
      • 您的测试中有几个偏差,特别是:(i) 您不允许在测量结果之前编译方法 (ii) mValue != value 始终为 true => 没有分支错误预测。
      • 另外请注意,在我的机器上,我得到了TOTAL(1)=1000000000 0ms,因为您没有对结果做任何事情,整个循环被编译器优化掉了......
      • 测试并不完美,但有方向性,是的,比较和设置并不更好...... ;-)
      • 不正确构建的测试实际上可能会产生误导(这是定向的,但方向错误)!
      猜你喜欢
      • 2010-09-18
      • 2012-09-12
      • 2013-11-18
      • 2012-03-05
      • 2016-05-29
      • 2014-05-17
      • 1970-01-01
      • 2012-08-21
      相关资源
      最近更新 更多