2013-02-28 38 views
16

Cảnh báo: câu hỏi này hơi dài một chút, nhưng phần bên dưới đường phân tách chỉ dành cho sự tò mò.Triển khai AtomicInteger và sao chép mã

JDK 7 thực hiện AtomicInteger của Oracle bao gồm các phương pháp sau đây:

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

Có vẻ như rõ ràng rằng phương pháp thứ hai có thể đã được viết:

public final int incrementAndGet() { 
    return addAndGet(1); 
} 

Có rất nhiều ví dụ khác về mã tương tự trùng lặp trong lớp đó. Tôi không thể nghĩ ra bất kỳ lý do nào để làm điều đó nhưng cân nhắc về hiệu suất (*). Và tôi khá chắc chắn các tác giả đã làm một số kiểm tra chuyên sâu trước khi giải quyết trên thiết kế đó.

Tại sao (hoặc trong hoàn cảnh nào) mã đầu tiên sẽ hoạt động tốt hơn lần thứ hai?


(*) Tôi không thể cưỡng lại nhưng viết một tiêu chuẩn siêu nhanh. Nó cho thấy (sau JIT) một khoảng cách có hệ thống hiệu suất 2-4% ủng hộ addAndGet(1) vs incrementAndGet() (đó là thừa nhận nhỏ, nhưng nó rất nhất quán). Tôi thực sự không thể giải thích kết quả mà một trong hai phải trung thực ...

Output:

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

Mã số:

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 Đối với 'JVM warmup' :) –

+1

Mặc dù tôi nghi ngờ điều này sẽ thực sự tiết lộ câu trả lời, bạn có thể tháo rời cả hai chức năng và hiển thị mã Java bytecode đã tháo rời ở đây. Nó * có thể * tiết lộ điều gì đó. –

Trả lời

6

Tôi sẽ đưa ra giả thuyết mới.Nếu chúng ta nhìn vào mã byte của AtomicInteger chúng ta sẽ thấy, đó là sự khác biệt chính giữa chúng là addAndGet sử dụng iload_ hướng dẫn, và incrementAndGet sử dụng iconst_ hướng dẫn:

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 

Dường như, đó iconst_ + iadd dịch là INC hướng dẫn, do iload_ ... iadd làm hướng dẫn ADD. Điều này tất cả liên quan đến thường được gọi câu hỏi về ADD 1 vs INC và vân vân:

Relative performance of x86 inc vs. add instruction

Is ADD 1 really faster than INC ? x86

Đây có thể là câu trả lời, tại sao addAndGet là hơi nhanh hơn incrementAndGet

+1

Xem câu trả lời của tôi, có vẻ như để xác nhận quan điểm của bạn. – assylias

+0

Câu trả lời ban đầu của bạn là [spot on] (http://cs.oswego.edu/pipermail/concurrency-interest/2013-February/010893.html)! Ngoại trừ việc nó đã không được thực hiện trong 7u11 nhưng rõ ràng (đó là phiên bản tôi đang sử dụng). – assylias

+0

Xem thêm [cơ sở dữ liệu lỗi] (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7023898). – assylias

-3

Lý do là chúng thích làm mã nhanh hơn, bằng kích thước mã.

Tôi chắc chắn, các nguồn là có thật. Nếu chúng là bản chất, chúng sẽ được đánh dấu là bản địa.

+0

Tôi chắc chắn đó là trường hợp - câu hỏi là "tại sao điều đó sẽ nhanh hơn?" – assylias

+1

Alexei, bạn sai về "nếu họ là bản chất, họ sẽ là markerd như bản địa" - có vẻ như, rằng bạn không hiểu những gì "nội tại" được. – Andremoniy

+0

@Andremoniy Tôi thực sự không hiểu "nội tại". bạn có thể thêm điều đó vào câu trả lời của bạn không? – irreputable

1

Để mở rộng câu trả lời của @ AlexeiKaigorodov, nếu đây là mã Java thực, nó sẽ nhanh hơn vì nó sẽ loại bỏ một khung phụ trên ngăn xếp cuộc gọi. Điều này làm cho nó chạy nhanh hơn (tại sao không?) Và có thể có ngụ ý rằng nhiều cuộc gọi đồng thời đến vòng lặp ít có khả năng thất bại, khiến cho vòng lặp chạy lặp lại. (Mặc dù vậy, tôi không thể nghĩ ra những lý do như vậy trên đầu của tôi.)

Mặc dù, thông qua các tiêu chuẩn vi mô của bạn, có thể mã không thực và phương pháp incrementAndGet() được triển khai trong mã gốc theo cách bạn đã chỉ định, hoặc cả hai chỉ là hướng dẫn nội tại (được giao cho lock:xadd trên x86 chẳng hạn). Tuy nhiên, nói chung là khá khó để đoán được những gì JVM đang làm mọi lúc, và có thể có những thứ khác đang gây ra điều này.

+0

Không chắc chắn tôi hiểu phần này "* ít có khả năng nhiều cuộc gọi đồng thời compareAndSet() sẽ thất bại và vòng lặp sẽ phải chạy lại. *": 'CompareAndSet' được chạy trên cùng một đối tượng' this' trong mọi trường hợp ... – assylias

+0

+1 cho 'loại bỏ khung phụ trên ngăn xếp cuộc gọi'. –

+0

'incrementAndGet' chạy chậm hơn' addAndGet', vì vậy việc loại bỏ khung phụ bạn đang nói đến là gì? – Andremoniy

4

Ra khỏi tò mò, đây là mã lắp ráp được tạo bởi JIT. Nói tóm lại, sự khác biệt chính là:

  • incrementAndGet

    mov r8d,eax 
    inc r8d    ;*iadd 
    
  • addAndGet

    mov r9d,r8d 
    add r9d,eax   ;*iadd 
    

Phần còn lại của các mã cơ bản là như nhau. Điều đó khẳng định rằng:

  • các phương pháp này không intrinsics và không gọi nhau dưới mui xe
  • sự khác biệt duy nhất là INC vs ADD 1

Tôi không đủ giỏi đọc lắp ráp để biết tại sao lại tạo nên sự khác biệt. Và điều đó không thực sự trả lời câu hỏi ban đầu của tôi.

Full niêm yết (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 

Full niêm yết (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

cách tạo assembly từ jit? –

+1

@SudipBhandari https://stackoverflow.com/a/15146962/829571 – assylias

Các vấn đề liên quan