2013-02-28 54 views
16

警告:问题有点长,但分隔线下面的部分只是为了好奇。AtomicInteger实施和代码复制

甲骨文的JDK 7的实施AtomicInteger包括以下方法:

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); 
} 

有类似的代码的其他几个例子该班的重复。我想不出有什么理由要这样做,但考虑到性能(*)。我很确定作者在完成这个设计之前做了一些深入的测试。

为什么(或者在什么情况下)第一个代码比第二个代码表现更好?


(*)我无法抗拒,但编写一个简单的微基准。它显示(post-JIT)2-4%性能的系统差距有利于addAndGet(1) vs incrementAndGet()(虽然这很小,但非常一致)。我真的不能解释的结果要么是诚实的......

输出:

incrementAndGet():905
addAndGet(1):868
incrementAndGet():902
addAndGet (1):863
incrementAndGet():891
addAndGet(1):867
...

代码:

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)); 
    } 
} 
+0

+1对于'JVM热身':) – 2013-02-28 18:35:22

+1

虽然我怀疑这实际上会揭示答案,但您可以反汇编这两个函数,并在这里显示反汇编的Java字节码。它*可能会揭示一些事情。 – 2013-02-28 18:50:12

回答

6

我会给出新的假设。如果我们考虑的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_ ...... iadd作为ADD指令。这一切都涉及到俗称的问题有关ADD 1 VS INC等:

Relative performance of x86 inc vs. add instruction

Is ADD 1 really faster than INC ? x86

这可能是答案,为什么addAndGet是略快于incrementAndGet

+1

看到我的答案,它似乎证实了你的观点。 – assylias 2013-02-28 19:49:06

+0

您的初始答案是[spot on](http://cs.oswego.edu/pipermail/concurrency-interest/2013-February/010893.html)!除了它在7u11还没有实现,但显然(这是我使用的版本)。 – assylias 2013-02-28 22:18:34

+0

另请参见[错误数据库](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7023898)。 – assylias 2013-02-28 22:19:19

-3

的原因是,他们宁愿使代码更快,代码大小为代价。

我相信,来源是真实的。如果它们是内在的,它们将被标记为本地的。

+0

我确定是这样的 - 问题是“为什么会更快?” – assylias 2013-02-28 18:24:57

+1

阿列克谢,你错了“如果他们是内在的,他们会被标记为本地人” - 看起来,你不明白什么是“内在的”。 – Andremoniy 2013-02-28 18:26:01

+0

@Andremoniy我也不太了解“内部函数”。你可以在你的答案中加入吗? – irreputable 2013-02-28 18:27:39

1

若要扩展@ AlexeiKaigorodov的答案,如果这是真正的Java代码,它会更快,因为它可以消除调用堆栈上的额外帧。这使得它运行得更快(为什么不?),并且可能会影响多个并发调用到循环不太可能失败,从而导致循环重复运行。 (虽然我不能想出任何这样的理由,但我的头顶。)

虽然,通过您的微基准,有可能代码是不真实的,并且incrementAndGet()方法在本机代码中实现按照您指定的方式执行,或者两者都只是内部指令(例如,在x86上委托给lock:xadd)。然而,通常很难理解JVM一直在做什么,并且可能还有其他一些事情正在造成这种情况。

+0

不确定我是否理解这部分“*不太可能多个并发调用compareAndSet()将失败,并且循环将不得不再次运行*”:'compareAndSet'在任何情况下都运行在相同的'this'对象上... – assylias 2013-02-28 18:28:31

+0

+1用于消除调用堆栈上的额外帧。 – 2013-02-28 18:34:26

+0

'incrementAndGet'运行速度比'addAndGet'慢,那么你正在讨论的额外帧的消除是什么? – Andremoniy 2013-02-28 18:43:41

4

出于好奇,这里是由JIT生成的汇编代码。总之,主要的区别是:

  • incrementAndGet

    mov r8d,eax 
    inc r8d    ;*iadd 
    
  • addAndGet

    mov r9d,r8d 
    add r9d,eax   ;*iadd 
    

的代码的其余部分基本上是相同的。这证实:

  • 方法是不是内与机罩
  • 下不打电话给对方,唯一的区别是INC VS ADD 1

我不是在读取组件到足够好知道为什么这有所作为。这并不能真正回答我最初的问题。

全面上市(incrementAndGet):

# {method} 'incrementAndGet' '()I' in 'java/util/concurrent/atomic/AtomicInteger' 
    #   [sp+0x20] (sp of caller) 
    0x00000000026804c0: mov r10d,DWORD PTR [rdx+0x8] 
    0x00000000026804c4: shl r10,0x3 
    0x00000000026804c8: cmp rax,r10 
    0x00000000026804cb: jne 0x0000000002657b60 ; {runtime_call} 
    0x00000000026804d1: data32 xchg ax,ax 
    0x00000000026804d4: nop DWORD PTR [rax+rax*1+0x0] 
    0x00000000026804dc: data32 data32 xchg ax,ax 
[Verified Entry Point] 
    0x00000000026804e0: sub rsp,0x18 
    0x00000000026804e7: mov QWORD PTR [rsp+0x10],rbp ;*synchronization entry 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 204) 
    0x00000000026804ec: mov eax,DWORD PTR [rdx+0xc] ;*invokevirtual compareAndSwapInt 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 135) 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 206) 
    0x00000000026804ef: mov r8d,eax 
    0x00000000026804f2: inc r8d    ;*iadd 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 205) 
    0x00000000026804f5: lock cmpxchg DWORD PTR [rdx+0xc],r8d 
    0x00000000026804fb: sete r11b 
    0x00000000026804ff: movzx r11d,r11b   ;*invokevirtual compareAndSwapInt 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 135) 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 206) 
    0x0000000002680503: test r11d,r11d 
    0x0000000002680506: je  0x0000000002680520 ;*iload_2 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 207) 
    0x0000000002680508: mov eax,r8d 
    0x000000000268050b: add rsp,0x10 
    0x000000000268050f: pop rbp 
    0x0000000002680510: test DWORD PTR [rip+0xfffffffffdbafaea],eax  # 0x0000000000230000 
               ; {poll_return} 
    0x0000000002680516: ret  
    0x0000000002680517: nop WORD PTR [rax+rax*1+0x0] ; OopMap{rdx=Oop off=96} 
               ;*goto 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 208) 
    0x0000000002680520: test DWORD PTR [rip+0xfffffffffdbafada],eax  # 0x0000000000230000 
               ;*goto 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 208) 
               ; {poll} 
    0x0000000002680526: mov r11d,DWORD PTR [rdx+0xc] ;*invokevirtual compareAndSwapInt 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 135) 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 206) 
    0x000000000268052a: mov r8d,r11d 
    0x000000000268052d: inc r8d    ;*iadd 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 205) 
    0x0000000002680530: mov eax,r11d 
    0x0000000002680533: lock cmpxchg DWORD PTR [rdx+0xc],r8d 
    0x0000000002680539: sete r11b 
    0x000000000268053d: movzx r11d,r11b   ;*invokevirtual compareAndSwapInt 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 135) 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 206) 
    0x0000000002680541: test r11d,r11d 
    0x0000000002680544: je  0x0000000002680520 ;*ifeq 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 206) 
    0x0000000002680546: jmp 0x0000000002680508 

全面上市(addAndGet):

# {method} 'addAndGet' '(I)I' in 'java/util/concurrent/atomic/AtomicInteger' 
    # this:  rdx:rdx = 'java/util/concurrent/atomic/AtomicInteger' 
    # parm0: r8  = int 
    #   [sp+0x20] (sp of caller) 
    0x0000000002680d00: mov r10d,DWORD PTR [rdx+0x8] 
    0x0000000002680d04: shl r10,0x3 
    0x0000000002680d08: cmp rax,r10 
    0x0000000002680d0b: jne 0x0000000002657b60 ; {runtime_call} 
    0x0000000002680d11: data32 xchg ax,ax 
    0x0000000002680d14: nop DWORD PTR [rax+rax*1+0x0] 
    0x0000000002680d1c: data32 data32 xchg ax,ax 
[Verified Entry Point] 
    0x0000000002680d20: sub rsp,0x18 
    0x0000000002680d27: mov QWORD PTR [rsp+0x10],rbp ;*synchronization entry 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 233) 
    0x0000000002680d2c: mov eax,DWORD PTR [rdx+0xc] ;*invokevirtual compareAndSwapInt 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 135) 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 235) 
    0x0000000002680d2f: mov r9d,r8d 
    0x0000000002680d32: add r9d,eax   ;*iadd 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 234) 
    0x0000000002680d35: lock cmpxchg DWORD PTR [rdx+0xc],r9d 
    0x0000000002680d3b: sete r11b 
    0x0000000002680d3f: movzx r11d,r11b   ;*invokevirtual compareAndSwapInt 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 135) 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 235) 
    0x0000000002680d43: test r11d,r11d 
    0x0000000002680d46: je  0x0000000002680d60 ;*iload_3 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 236) 
    0x0000000002680d48: mov eax,r9d 
    0x0000000002680d4b: add rsp,0x10 
    0x0000000002680d4f: pop rbp 
    0x0000000002680d50: test DWORD PTR [rip+0xfffffffffdbaf2aa],eax  # 0x0000000000230000 
               ; {poll_return} 
    0x0000000002680d56: ret  
    0x0000000002680d57: nop WORD PTR [rax+rax*1+0x0] ; OopMap{rdx=Oop off=96} 
               ;*goto 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 237) 
    0x0000000002680d60: test DWORD PTR [rip+0xfffffffffdbaf29a],eax  # 0x0000000000230000 
               ;*goto 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 237) 
               ; {poll} 
    0x0000000002680d66: mov r11d,DWORD PTR [rdx+0xc] ;*invokevirtual compareAndSwapInt 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 135) 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 235) 
    0x0000000002680d6a: mov r9d,r11d 
    0x0000000002680d6d: add r9d,r8d   ;*iadd 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 234) 
    0x0000000002680d70: mov eax,r11d 
    0x0000000002680d73: lock cmpxchg DWORD PTR [rdx+0xc],r9d 
    0x0000000002680d79: sete r11b 
    0x0000000002680d7d: movzx r11d,r11b   ;*invokevirtual compareAndSwapInt 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 135) 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 235) 
    0x0000000002680d81: test r11d,r11d 
    0x0000000002680d84: je  0x0000000002680d60 ;*ifeq 
               ; - java.util.concurrent.atomic.AtomicInteger::[email protected] (line 235) 
    0x0000000002680d86: jmp 0x0000000002680d48 
+0

如何从jit生成程序集? – 2017-05-31 09:40:31

+1

@SudipBhandari https://stackoverflow.com/a/15146962/829571 – assylias 2017-05-31 10:46:34