Java 值比较和值分配之间是否存在性能差异?

Java 值比较和值分配之间是否存在性能差异?,java,performance,optimization,jvm,atomic,Java,Performance,Optimization,Jvm,Atomic,考虑以下两个代码示例: 示例1。 public void setValue(int value) { mValue = value; } public void setValue(int value) { if (mValue != value) { mValue = value; } } // Test Code public class CompareAndSet { int mValue; // 1 public

考虑以下两个代码示例:

示例1。

public void setValue(int value)
{
    mValue = value;
}
public void setValue(int value)
{
    if (mValue != value)
    {
        mValue = value;
    }
}
// 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");
            }
        }
    }

}
示例2。

public void setValue(int value)
{
    mValue = value;
}
public void setValue(int value)
{
    if (mValue != value)
    {
        mValue = value;
    }
}
// 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");
            }
        }
    }

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


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

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

唯一确定答案的方法是在有代表性的输入上评测代码。有关如何设置有意义的微基准的一些提示,请参见


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

您可以尝试编译这两个代码,然后使用java字节码反汇编程序来检查差异。也许是个好的开始

根据我对所使用的编译器和VM的了解,由于额外的比较操作,第二个代码片段可能会稍微慢一点


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

NPE有很好的建议。我要补充的一点意见是,这可能还取决于
mValue
是否易变,以及运行它的硬件。对于挥发物,写操作的成本可能是读操作的几倍。一种是一些硬件(例如,我相信许多移动设备)的易失性读取非常不便宜,因此差异可能会减小。

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

但代码测试表明:

  • 总计(1)=100000000 1420毫秒总计(2)=100000000 2155毫秒
  • 总计(1)=10000000001374ms总计(2)=10000000001884ms
  • 总计(1)=10000000001379ms总计(2)=10000000002072ms
比较和设置并不是更好。

public void setValue(int value)
{
    mValue = value;
}
public void setValue(int value)
{
    if (mValue != value)
    {
        mValue = value;
    }
}
// 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");
            }
        }
    }

}
//测试代码
公共类比较数据集{
int值;
// 1
public void setValue1(int值){
mValue=值;
}
// 2
public void setValue2(int值){
if(mValue!=值){
mValue=值;
}
}
公共静态void main(字符串[]args){
最终整数总计=(整数)1e9;
长ts;
对于(int j=0;j<3;j++){
// 1
{
ts=系统.currentTimeMillis();
CompareAndSet cs=新的CompareAndSet();
对于(int i=0;i
“超出所有常识”-确实如此。收益部分取决于您将相同值分配给
mValue
的频率。如果这种情况很少发生,那么比较是不必要的开销。如果您非常幸运,条件分支将很容易预测,并且不会比典型操作更昂贵。如果您运气不好,它将不得不在执行的时间的很大一部分上刷新管道。下面@yshavit的观点适用于所有并发访问,而不管字段是否易变。您正在用一个分支换取一个可能毫无意义的写未命中,对于给定的字段更改概率来说,这可能是一个很好的交易。如果将“setValueX(i)”更改为setValueX(1),则时间为(比较和设置总是不好):-TOTAL(1)=10000000001390ms TOTAL(2)=10000000001386ms-TOTAL(1)=10000000001354ms TOTAL(2)=10000000001353ms-TOTAL(1) =10000000001398ms总计(2)=10000000001389ms在您的测试中有几个偏差,特别是:(i)您不允许在测量结果之前编译该方法(ii)
mValue!=value
始终为真=>没有分支预测失误。还要注意,在我的机器上,我得到
TOTAL(1)=1000000000毫秒
因为您不处理结果,编译器会优化整个循环…测试并不完美,但定向是的,比较和设置并不更好…;-)不正确构建的测试实际上可能会产生误导(定向,但方向错误)!