值比较和值赋值之间是否有性能差异?

4
考虑以下两个代码示例:
示例1: ```javascript var x = 5; var y = 6; var z = x + y; ```
示例2: ```javascript var x = 5, y = 6; var z = x + y; ```
这两个示例的功能是相同的。但是,示例2使用了逗号操作符来声明和初始化变量x和y。这使得代码更加简洁易读,在一些情况下可能会更方便。
public void setValue(int value)
{
    mValue = value;
}

例子2.

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

假设您被要求将一些Java代码优化到绝对最大的程度,超越所有常识。

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


7
“超出了所有常识” - 的确。 - duffymo
1
收益部分取决于您多久将相同的值分配给mValue。 如果这种情况很少发生,则比较是不必要的开销。 - Code-Apprentice
4个回答

9
第二种可能是反优化,因为在现代处理器上分支往往是昂贵的(主要是因为分支误判非常昂贵)。要确定这一点的唯一方法是在代表性输入上对代码进行分析。有关如何设置有意义的微基准测试,请参见 How do I write a correct micro-benchmark in Java?。请注意,如果所涉及的代码不占总CPU时间的重要部分,则世界上所有的优化都不会对应用程序的整体性能产生太大的影响。

3
如果你非常幸运,条件分支将很容易预测,并且不会比典型操作更昂贵。如果你不幸的话,它将不得不在执行的相当大一部分时间内清空流水线。 - Patricia Shanahan
@yshavit所提出的观点适用于所有并发访问,无论字段是否为volatile。您正在交换分支以获取可能是无意义的写入未命中,这可能是对字段更改概率给定的情况下的良好交易。 - Nitsan Wakart

3

NPE提供了很好的建议。我想补充一点,这也取决于mValue是否为volatile以及你正在运行的硬件。对于volatiles,写入操作比读取操作的开销要高几倍。在某些硬件上(例如许多移动设备),volatile读取操作的代价相当高,因此这种差异可能会降低。


1

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

根据我使用过的编译器和虚拟机的理解,由于额外的比较操作,第二个代码片段可能会稍微慢一些。

如果您的JVM实现具有优化器,则可能能够将您的第一个代码示例减少为一个操作符,而第二个代码片段将需要至少一到两个操作符来解释比较操作。


0

良好的编码实践是:在最后一刻之前延迟优化。

但代码测试表明:

  • 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
- ggrandes
1
你的测试中有几个偏见,特别是:(i) 在测量结果之前,不允许编译方法 (ii) mValue != value 总是为真 => 没有分支预测错误。 - assylias
1
还要注意,在我的机器上,我得到了TOTAL(1)=1000000000 0ms,因为你没有对结果进行任何操作,整个循环被编译器优化掉了... - assylias
测试并不完美,但是具有指导意义,比较和设置也不是更好的选择...;-) - ggrandes
使用JMH来避免陷阱。 - Nitsan Wakart
显示剩余2条评论

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