警告:问题有点长,但分隔线下方的部分仅出于好奇。

Oracle的AtomicInteger的JDK 7实现包括以下方法:

public final int addAndGet(int delta) {
    for (;;) {
        int current = get();
        int next = current + delta;         // Only difference
        if (compareAndSet(current, next))
            return next;
    }
}

public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;             // Only difference
        if (compareAndSet(current, next))
            return next;
    }
}

显然,第二种方法可以编写为:
public final int incrementAndGet() {
    return addAndGet(1);
}

该类中还有其他几个类似的代码重复示例。除了性能方面的考虑(*),我无法想到这样做的任何原因。而且我很确定作者在确定该设计之前进行了一些深入的测试。

为什么(在什么情况下)第一个代码比第二个代码性能更好?

(*)我忍不住要写一个快速的微型基准测试。它显示(JIT之后)系统性能存在2-4%的差距,而addAndGet(1)incrementAndGet()相比(虽然很小,但非常一致)。老实说,我无法真正解释这个结果。

输出:



码:
public static void main(String[] args) throws Exception {
    final int size = 100_000_000;
    long start, end;
    AtomicInteger ai;

    System.out.println("JVM warmup");
    for (int j = 0; j < 10; j++) {
        start = System.nanoTime();
        ai = new AtomicInteger();
        for (int i = 0; i < size / 10; i++) {
            ai.addAndGet(1);
        }
        end = System.nanoTime();
        System.out.println("addAndGet(1): " + ((end - start) / 1_000_000));
        start = System.nanoTime();
        ai = new AtomicInteger();
        for (int i = 0; i < size / 10; i++) {
            ai.incrementAndGet();
        }
        end = System.nanoTime();
        System.out.println("incrementAndGet(): " + ((end - start) / 1_000_000));
    }


    System.out.println("\nStart measuring\n");

    for (int j = 0; j < 10; j++) {
        start = System.nanoTime();
        ai = new AtomicInteger();
        for (int i = 0; i < size; i++) {
            ai.incrementAndGet();
        }
        end = System.nanoTime();
        System.out.println("incrementAndGet(): " + ((end - start) / 1_000_000));
        start = System.nanoTime();
        ai = new AtomicInteger();
        for (int i = 0; i < size; i++) {
            ai.addAndGet(1);
        }
        end = System.nanoTime();
        System.out.println("addAndGet(1): " + ((end - start) / 1_000_000));
    }
}

最佳答案

我给新的假设。如果我们查看AtomicInteger的字节码,我们将发现它们之间的主要区别在于addAndGet使用iload_指令,而incrementAndGet使用iconst_指令:

public final int addAndGet(int);
   ...
   4:   istore_2
   5:   iload_2
   6:   iload_1
   7:   iadd

public final int incrementAndGet();
   ...
   4:   istore_1
   5:   iload_1
   6:   iconst_1
   7:   iadd

似乎iconst_ + iadd转换为INC指令,这是因为iload_ ... iaddADD指令。所有这些都与关于ADD 1INC等的众所周知的问题有关:

Relative performance of x86 inc vs. add instruction

Is ADD 1 really faster than INC ? x86

这可能就是答案,为什么addAndGetincrementAndGet快一点

07-24 09:38
查看更多