Tại sao Java chuyển đổi trên các int liền kề dường như chạy nhanh hơn với các trường hợp được thêm vào?


276

Tôi đang làm việc trên một số mã Java cần được tối ưu hóa cao vì nó sẽ chạy trong các hàm nóng được gọi ở nhiều điểm trong logic chương trình chính của tôi. Một phần của mã này liên quan đến việc nhân doublecác biến bằng cách 10tăng lên int exponents không âm . Một cách nhanh chóng (chỉnh sửa: nhưng không phải là nhanh nhất có thể, xem Cập nhật 2 bên dưới) để có được giá trị nhân là switchtrên exponent:

double multiplyByPowerOfTen(final double d, final int exponent) {
   switch (exponent) {
      case 0:
         return d;
      case 1:
         return d*10;
      case 2:
         return d*100;
      // ... same pattern
      case 9:
         return d*1000000000;
      case 10:
         return d*10000000000L;
      // ... same pattern with long literals
      case 18:
         return d*1000000000000000000L;
      default:
         throw new ParseException("Unhandled power of ten " + power, 0);
   }
}

Các dấu chấm lửng nhận xét ở trên chỉ ra rằng các case inthằng số tiếp tục tăng thêm 1, vì vậy thực sự có 19 casegiây trong đoạn mã trên. Kể từ khi tôi đã không chắc chắn cho dù tôi thực sự sẽ cần tất cả các quyền hạn của 10 trong casebáo cáo 10qua 18, tôi chạy một số microbenchmarks so sánh thời gian để hoàn thành 10 triệu hoạt động với điều này switchtuyên bố so với switchchỉ case0qua 9(với exponentgiới hạn 9 hoặc ít hơn để tránh phá vỡ xuống switch). Tôi đã nhận được một kết quả khá ngạc nhiên (với tôi, ít nhất là!) Rằng càng lâu switchvới nhiều casecâu lệnh thực sự chạy nhanh hơn.

Trong một lần loay hoay, tôi đã thử thêm nhiều cases mà chỉ trả về các giá trị giả, và thấy rằng tôi có thể khiến công tắc chạy nhanh hơn với khoảng 22-27 cases (mặc dù các trường hợp giả đó không bao giờ thực sự bị tấn công trong khi mã đang chạy ). (Một lần nữa, cases được thêm vào một cách liền kề bằng cách tăng casehằng số trước đó 1.) Những khác biệt về thời gian thực hiện này không đáng kể: đối với ngẫu nhiên exponentgiữa 010, switchcâu lệnh giả đệm kết thúc 10 triệu lần thực hiện trong 1,49 giây so với 1,54 giây đối với phần không được đệm phiên bản, với tổng số tiền tiết kiệm lớn là 5ns cho mỗi lần thực hiện. Vì vậy, không phải là thứ khiến cho việc ám ảnh về việc đệm raswitchtuyên bố giá trị nỗ lực từ một quan điểm tối ưu hóa. Nhưng tôi vẫn chỉ thấy tò mò và phản trực giác rằng việc switchkhông trở nên chậm hơn (hoặc có lẽ tốt nhất là duy trì thời gian O (1) liên tục ) để thực thi khi thêm nhiều cases vào nó.

chuyển kết quả điểm chuẩn

Đây là những kết quả tôi thu được từ việc chạy với các giới hạn khác nhau trên các exponentgiá trị được tạo ngẫu nhiên . Tôi đã không bao gồm các kết quả cho đến 1hết exponentgiới hạn, nhưng hình dạng chung của đường cong vẫn giữ nguyên, với một sườn núi xung quanh dấu hiệu trường hợp 12-17 và một thung lũng trong khoảng 18-28. Tất cả các thử nghiệm đã được chạy trong JUnitBenchmark bằng cách sử dụng các thùng chứa chung cho các giá trị ngẫu nhiên để đảm bảo các đầu vào thử nghiệm giống hệt nhau. Tôi cũng đã chạy các bài kiểm tra theo thứ tự từ switchcâu lệnh dài nhất đến câu ngắn nhất và ngược lại, để thử và loại bỏ khả năng các vấn đề kiểm tra liên quan đến đơn hàng. Tôi đã đặt mã kiểm tra của mình lên một repo github nếu có ai muốn thử tạo lại các kết quả này.

Vì vậy, những gì đang xảy ra ở đây? Một số mơ hồ về kiến ​​trúc của tôi hoặc xây dựng chuẩn vi mô? Hoặc là Java switchthực sự nhanh hơn một chút để thực hiện trong 18đến 28 casephạm vi hơn là từ 11lên đến 17?

thử nghiệm github repo "thí nghiệm chuyển đổi"

CẬP NHẬT: Tôi đã dọn sạch thư viện điểm chuẩn một chút và thêm một tệp văn bản vào / kết quả với một số đầu ra trên một phạm vi rộng hơn của các exponentgiá trị có thể . Tôi cũng đã thêm một tùy chọn trong mã kiểm tra để không ném Exceptiontừ đó default, nhưng điều này dường như không ảnh hưởng đến kết quả.

CẬP NHẬT 2: Tìm thấy một số thảo luận khá tốt về vấn đề này từ năm 2009 trên diễn đàn xkcd tại đây: http://forums.xkcd.com/viewtopic.php?f=11&t=33524 . Cuộc thảo luận về việc sử dụng của OP Array.binarySearch()đã cho tôi ý tưởng về việc triển khai mô hình lũy thừa đơn giản ở trên. Không cần tìm kiếm nhị phân vì tôi biết các mục trong đó arraylà gì. Nó dường như chạy nhanh hơn khoảng 3 lần so với việc sử dụng switch, rõ ràng là phải trả giá bằng một số luồng điều khiển switchcó liên quan. Mã đó cũng đã được thêm vào repo github.


64
Bây giờ tất cả các nhân viên Google ở ​​khắp mọi nơi sẽ có chính xác 22 trường hợp trong tất cả các switchbáo cáo, vì rõ ràng đây là giải pháp tối ưu nhất. : D (Đừng thể hiện điều này với sự dẫn dắt của tôi, làm ơn.)
asteri 25/03/13

2
Bạn có một SSCCE đơn giản hơn? Cái này không biên dịch cho tôi. Yếu như tôi với hiệu năng Java, tôi muốn chụp ảnh này.
Bí ẩn

5
Bạn có thể thấy phần "Chuyển đổi trong JVM" trong câu trả lời của tôi về các trường hợp dựa trên chuỗi hữu ích. Tôi nghĩ những gì đang xảy ra ở đây là bạn đang chuyển từ a lookupswitchsang a tableswitch. Việc phân tách mã của bạn với javapsẽ cho bạn thấy chắc chắn.
erickson

2
Tôi đã thêm các lọ phụ thuộc vào thư mục / lib trong repo. @Mysticial Xin lỗi, tôi đã dành quá nhiều thời gian để chui xuống cái hố thỏ này! Nếu bạn loại bỏ "extends AbstractBenchmark" khỏi các lớp kiểm tra và loại bỏ nhập khẩu "com.carrotsearch", bạn có thể chạy chỉ với phụ thuộc JUnit, nhưng công cụ tìm kiếm carrots khá tốt để lọc một số tiếng ồn từ JIT và thời gian khởi động. Thật không may, tôi không biết làm thế nào để chạy các bài kiểm tra JUnit này bên ngoài IntelliJ.
Andrew Bissell

2
@AndrewBissell Tôi đã quản lý để kiểm tra lại kết quả của bạn với điểm chuẩn đơn giản hơn nhiều. Chi nhánh so với bảng cho hiệu suất nhỏ so với kích thước trung bình là một phỏng đoán có phần rõ ràng. Nhưng tôi không có cái nhìn sâu sắc hơn bất kỳ ai khác về việc nhúng vào 30 trường hợp ...
Bí ẩn

Câu trả lời:


228

Như được chỉ ra bởi câu trả lời khác , bởi vì các giá trị trường hợp là liền kề (trái ngược với thưa thớt), mã byte được tạo cho các thử nghiệm khác nhau của bạn sử dụng bảng chuyển đổi (hướng dẫn mã byte tableswitch).

Tuy nhiên, một khi JIT bắt đầu công việc của nó và biên dịch mã byte thành cụm, tableswitchlệnh không phải lúc nào cũng dẫn đến một loạt các con trỏ: đôi khi bảng chuyển đổi được chuyển thành trông giống như lookupswitch(tương tự cấu trúc if/ else if).

Dịch ngược cụm được tạo bởi JIT (hotspot JDK 1.7) cho thấy rằng nó sử dụng một chuỗi if / khác nếu có 17 trường hợp hoặc ít hơn, một mảng các con trỏ khi có nhiều hơn 18 (hiệu quả hơn).

Lý do tại sao số ma thuật 18 này được sử dụng dường như đi xuống giá trị mặc định của MinJumpTableSizecờ JVM (khoảng dòng 352 trong mã).

Tôi đã nêu vấn đề trên danh sách trình biên dịch hotspot và nó dường như là một di sản của quá trình thử nghiệm trước đây . Lưu ý rằng giá trị mặc định này đã bị xóa trong JDK 8 sau khi thực hiện thêm điểm chuẩn .

Cuối cùng, khi phương thức trở nên quá dài (> 25 trường hợp trong các thử nghiệm của tôi), nó không được nội tuyến nữa với các cài đặt JVM mặc định - đó là nguyên nhân thích hợp nhất làm giảm hiệu suất tại thời điểm đó.


Với 5 trường hợp, mã dịch ngược trông như thế này (chú ý các hướng dẫn cmp / je / jg / jmp, tập hợp cho if / goto):

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x00000000024f0160: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x00000000024f0167: push   rbp
  0x00000000024f0168: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x00000000024f016c: cmp    edx,0x3
  0x00000000024f016f: je     0x00000000024f01c3
  0x00000000024f0171: cmp    edx,0x3
  0x00000000024f0174: jg     0x00000000024f01a5
  0x00000000024f0176: cmp    edx,0x1
  0x00000000024f0179: je     0x00000000024f019b
  0x00000000024f017b: cmp    edx,0x1
  0x00000000024f017e: jg     0x00000000024f0191
  0x00000000024f0180: test   edx,edx
  0x00000000024f0182: je     0x00000000024f01cb
  0x00000000024f0184: mov    ebp,edx
  0x00000000024f0186: mov    edx,0x17
  0x00000000024f018b: call   0x00000000024c90a0  ; OopMap{off=48}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@72 (line 83)
                                                ;   {runtime_call}
  0x00000000024f0190: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@72 (line 83)
  0x00000000024f0191: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffffa7]        # 0x00000000024f0140
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@52 (line 62)
                                                ;   {section_word}
  0x00000000024f0199: jmp    0x00000000024f01cb
  0x00000000024f019b: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff8d]        # 0x00000000024f0130
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@46 (line 60)
                                                ;   {section_word}
  0x00000000024f01a3: jmp    0x00000000024f01cb
  0x00000000024f01a5: cmp    edx,0x5
  0x00000000024f01a8: je     0x00000000024f01b9
  0x00000000024f01aa: cmp    edx,0x5
  0x00000000024f01ad: jg     0x00000000024f0184  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x00000000024f01af: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff81]        # 0x00000000024f0138
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@64 (line 66)
                                                ;   {section_word}
  0x00000000024f01b7: jmp    0x00000000024f01cb
  0x00000000024f01b9: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff67]        # 0x00000000024f0128
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@70 (line 68)
                                                ;   {section_word}
  0x00000000024f01c1: jmp    0x00000000024f01cb
  0x00000000024f01c3: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff55]        # 0x00000000024f0120
                                                ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
                                                ;   {section_word}
  0x00000000024f01cb: add    rsp,0x10
  0x00000000024f01cf: pop    rbp
  0x00000000024f01d0: test   DWORD PTR [rip+0xfffffffffdf3fe2a],eax        # 0x0000000000430000
                                                ;   {poll_return}
  0x00000000024f01d6: ret    

Với 18 trường hợp, hội đồng trông như thế này (chú ý mảng con trỏ được sử dụng và loại bỏ sự cần thiết của tất cả các phép so sánh: jmp QWORD PTR [r8+r10*1]nhảy trực tiếp đến phép nhân đúng) - đó là lý do có khả năng cải thiện hiệu suất:

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x000000000287fe20: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x000000000287fe27: push   rbp
  0x000000000287fe28: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x000000000287fe2c: cmp    edx,0x13
  0x000000000287fe2f: jae    0x000000000287fe46
  0x000000000287fe31: movsxd r10,edx
  0x000000000287fe34: shl    r10,0x3
  0x000000000287fe38: movabs r8,0x287fd70       ;   {section_word}
  0x000000000287fe42: jmp    QWORD PTR [r8+r10*1]  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x000000000287fe46: mov    ebp,edx
  0x000000000287fe48: mov    edx,0x31
  0x000000000287fe4d: xchg   ax,ax
  0x000000000287fe4f: call   0x00000000028590a0  ; OopMap{off=52}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@202 (line 96)
                                                ;   {runtime_call}
  0x000000000287fe54: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@202 (line 96)
  0x000000000287fe55: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe8b]        # 0x000000000287fce8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@194 (line 92)
                                                ;   {section_word}
  0x000000000287fe5d: jmp    0x000000000287ff16
  0x000000000287fe62: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe86]        # 0x000000000287fcf0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@188 (line 90)
                                                ;   {section_word}
  0x000000000287fe6a: jmp    0x000000000287ff16
  0x000000000287fe6f: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe81]        # 0x000000000287fcf8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@182 (line 88)
                                                ;   {section_word}
  0x000000000287fe77: jmp    0x000000000287ff16
  0x000000000287fe7c: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe7c]        # 0x000000000287fd00
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@176 (line 86)
                                                ;   {section_word}
  0x000000000287fe84: jmp    0x000000000287ff16
  0x000000000287fe89: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe77]        # 0x000000000287fd08
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@170 (line 84)
                                                ;   {section_word}
  0x000000000287fe91: jmp    0x000000000287ff16
  0x000000000287fe96: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe72]        # 0x000000000287fd10
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@164 (line 82)
                                                ;   {section_word}
  0x000000000287fe9e: jmp    0x000000000287ff16
  0x000000000287fea0: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe70]        # 0x000000000287fd18
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@158 (line 80)
                                                ;   {section_word}
  0x000000000287fea8: jmp    0x000000000287ff16
  0x000000000287feaa: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe6e]        # 0x000000000287fd20
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@152 (line 78)
                                                ;   {section_word}
  0x000000000287feb2: jmp    0x000000000287ff16
  0x000000000287feb4: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe24]        # 0x000000000287fce0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@146 (line 76)
                                                ;   {section_word}
  0x000000000287febc: jmp    0x000000000287ff16
  0x000000000287febe: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe6a]        # 0x000000000287fd30
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@140 (line 74)
                                                ;   {section_word}
  0x000000000287fec6: jmp    0x000000000287ff16
  0x000000000287fec8: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe68]        # 0x000000000287fd38
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@134 (line 72)
                                                ;   {section_word}
  0x000000000287fed0: jmp    0x000000000287ff16
  0x000000000287fed2: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe66]        # 0x000000000287fd40
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@128 (line 70)
                                                ;   {section_word}
  0x000000000287feda: jmp    0x000000000287ff16
  0x000000000287fedc: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe64]        # 0x000000000287fd48
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@122 (line 68)
                                                ;   {section_word}
  0x000000000287fee4: jmp    0x000000000287ff16
  0x000000000287fee6: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe62]        # 0x000000000287fd50
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@116 (line 66)
                                                ;   {section_word}
  0x000000000287feee: jmp    0x000000000287ff16
  0x000000000287fef0: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe60]        # 0x000000000287fd58
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@110 (line 64)
                                                ;   {section_word}
  0x000000000287fef8: jmp    0x000000000287ff16
  0x000000000287fefa: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe5e]        # 0x000000000287fd60
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@104 (line 62)
                                                ;   {section_word}
  0x000000000287ff02: jmp    0x000000000287ff16
  0x000000000287ff04: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe5c]        # 0x000000000287fd68
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@98 (line 60)
                                                ;   {section_word}
  0x000000000287ff0c: jmp    0x000000000287ff16
  0x000000000287ff0e: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe12]        # 0x000000000287fd28
                                                ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
                                                ;   {section_word}
  0x000000000287ff16: add    rsp,0x10
  0x000000000287ff1a: pop    rbp
  0x000000000287ff1b: test   DWORD PTR [rip+0xfffffffffd9b00df],eax        # 0x0000000000230000
                                                ;   {poll_return}
  0x000000000287ff21: ret    

Và cuối cùng, hội đồng với 30 trường hợp (bên dưới) trông tương tự như 18 trường hợp, ngoại trừ phần bổ sung movapd xmm0,xmm1xuất hiện ở giữa mã, như được phát hiện bởi @cHao - tuy nhiên lý do thích hợp nhất cho hiệu suất giảm là do phương thức này quá dài để được nội tuyến với các cài đặt JVM mặc định:

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x0000000002524560: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x0000000002524567: push   rbp
  0x0000000002524568: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x000000000252456c: movapd xmm1,xmm0
  0x0000000002524570: cmp    edx,0x1f
  0x0000000002524573: jae    0x0000000002524592  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x0000000002524575: movsxd r10,edx
  0x0000000002524578: shl    r10,0x3
  0x000000000252457c: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe3c]        # 0x00000000025243c0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@364 (line 118)
                                                ;   {section_word}
  0x0000000002524584: movabs r8,0x2524450       ;   {section_word}
  0x000000000252458e: jmp    QWORD PTR [r8+r10*1]  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x0000000002524592: mov    ebp,edx
  0x0000000002524594: mov    edx,0x31
  0x0000000002524599: xchg   ax,ax
  0x000000000252459b: call   0x00000000024f90a0  ; OopMap{off=64}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@370 (line 120)
                                                ;   {runtime_call}
  0x00000000025245a0: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@370 (line 120)
  0x00000000025245a1: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe27]        # 0x00000000025243d0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@358 (line 116)
                                                ;   {section_word}
  0x00000000025245a9: jmp    0x0000000002524744
  0x00000000025245ae: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe22]        # 0x00000000025243d8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@348 (line 114)
                                                ;   {section_word}
  0x00000000025245b6: jmp    0x0000000002524744
  0x00000000025245bb: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe1d]        # 0x00000000025243e0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@338 (line 112)
                                                ;   {section_word}
  0x00000000025245c3: jmp    0x0000000002524744
  0x00000000025245c8: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe18]        # 0x00000000025243e8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@328 (line 110)
                                                ;   {section_word}
  0x00000000025245d0: jmp    0x0000000002524744
  0x00000000025245d5: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe13]        # 0x00000000025243f0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@318 (line 108)
                                                ;   {section_word}
  0x00000000025245dd: jmp    0x0000000002524744
  0x00000000025245e2: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe0e]        # 0x00000000025243f8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@308 (line 106)
                                                ;   {section_word}
  0x00000000025245ea: jmp    0x0000000002524744
  0x00000000025245ef: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe09]        # 0x0000000002524400
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@298 (line 104)
                                                ;   {section_word}
  0x00000000025245f7: jmp    0x0000000002524744
  0x00000000025245fc: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe04]        # 0x0000000002524408
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@288 (line 102)
                                                ;   {section_word}
  0x0000000002524604: jmp    0x0000000002524744
  0x0000000002524609: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdff]        # 0x0000000002524410
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@278 (line 100)
                                                ;   {section_word}
  0x0000000002524611: jmp    0x0000000002524744
  0x0000000002524616: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdfa]        # 0x0000000002524418
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@268 (line 98)
                                                ;   {section_word}
  0x000000000252461e: jmp    0x0000000002524744
  0x0000000002524623: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffd9d]        # 0x00000000025243c8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@258 (line 96)
                                                ;   {section_word}
  0x000000000252462b: jmp    0x0000000002524744
  0x0000000002524630: movapd xmm0,xmm1
  0x0000000002524634: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe0c]        # 0x0000000002524448
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@242 (line 92)
                                                ;   {section_word}
  0x000000000252463c: jmp    0x0000000002524744
  0x0000000002524641: movapd xmm0,xmm1
  0x0000000002524645: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffddb]        # 0x0000000002524428
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@236 (line 90)
                                                ;   {section_word}
  0x000000000252464d: jmp    0x0000000002524744
  0x0000000002524652: movapd xmm0,xmm1
  0x0000000002524656: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdd2]        # 0x0000000002524430
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@230 (line 88)
                                                ;   {section_word}
  0x000000000252465e: jmp    0x0000000002524744
  0x0000000002524663: movapd xmm0,xmm1
  0x0000000002524667: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdc9]        # 0x0000000002524438
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@224 (line 86)
                                                ;   {section_word}

[etc.]

  0x0000000002524744: add    rsp,0x10
  0x0000000002524748: pop    rbp
  0x0000000002524749: test   DWORD PTR [rip+0xfffffffffde1b8b1],eax        # 0x0000000000340000
                                                ;   {poll_return}
  0x000000000252474f: ret    

7
@ syb0rg Thành thật mà nói tôi cũng không hiểu chi tiết tốt ;-)
assylias

4
+1 cho câu trả lời tuyệt vời! Bạn có thể tháo rời thứ gì đó với hơn 30 trường hợp để so sánh khi hiệu suất thoát khỏi "nhúng" trong biểu đồ của OP không?
asteri


2
@AndrewBissell Tôi đoán là hành vi khác nhau dựa trên (i) các thử nghiệm hiệu năng kiến ​​trúc chéo đã chỉ ra rằng mảng con trỏ chỉ hiệu quả khi số lượng trường hợp lớn hơn 18 hoặc (ii) mã được định hình là nó được chạy và trình lược tả xác định cách tiếp cận nào tốt hơn trong thời gian chạy. Tôi không thể tìm thấy câu trả lời.
assylias

3
Việc tháo gỡ 30 trường hợp và một trường hợp 18 trường hợp trông giống nhau. Sự khác biệt dường như chủ yếu giới hạn ở một chút thêm đăng ký xáo trộn sau khoảng trường hợp thứ 11. Không thể nói tại sao JITter làm điều đó; nó xuất hiện không cần thiết
cHao

46

Switch - case nhanh hơn nếu các giá trị case được đặt trong phạm vi hẹp Eg.

case 1:
case 2:
case 3:
..
..
case n:

Bởi vì, trong trường hợp này, trình biên dịch có thể tránh thực hiện so sánh cho mọi chân trường hợp trong câu lệnh switch. Trình biên dịch tạo một bảng nhảy chứa các địa chỉ của các hành động được thực hiện trên các chân khác nhau. Giá trị mà công tắc đang được thực hiện được thao tác để chuyển đổi nó thành một chỉ mục thành jump table. Trong triển khai này, thời gian thực hiện trong câu lệnh chuyển đổi ít hơn nhiều so với thời gian trong một câu lệnh if-other-if tương đương. Ngoài ra thời gian thực hiện trong câu lệnh chuyển đổi không phụ thuộc vào số lượng chân trường hợp trong câu lệnh chuyển đổi.

Như đã nêu trong wikipedia về câu lệnh chuyển đổi trong phần Biên dịch.

Nếu phạm vi của các giá trị đầu vào được xác định là 'nhỏ' và chỉ có một vài khoảng trống, một số trình biên dịch kết hợp trình tối ưu hóa thực sự có thể thực hiện câu lệnh chuyển đổi như một bảng nhánh hoặc một mảng các con trỏ hàm được lập chỉ mục thay vì một chuỗi các lệnh có điều kiện dài. Điều này cho phép câu lệnh switch xác định ngay lập tức nhánh nào sẽ thực thi mà không cần phải đi qua danh sách so sánh.


4
Điều đó không đúng. Nó sẽ nhanh hơn bất kể giá trị trường hợp là hẹp hay rộng trong phạm vi. Đó là O (1) - không quan trọng cách các giá trị trường hợp cách nhau.
Aniket Inge

6
@Aniket: Đọc bài viết này của wikipedia. vi.wikipedia.org/wiki/Branch_table
Vishal K

14
@Aniket: Không phải là O (1) nếu phạm vi rộng và thưa thớt. Có hai loại công tắc và nếu phạm vi quá rộng, Java sẽ biên dịch nó thành một "tra cứu" thay vì "chuyển đổi bảng". Cái trước đòi hỏi một sự so sánh cho mỗi chi nhánh cho đến khi tìm thấy một cái, trong khi cái sau thì không.
cHao

4
Wikipedia là một nơi tốt để tìm tài liệu tham khảo, nhưng không nên được coi là một nguồn có thẩm quyền. Bất cứ điều gì bạn đọc ở đó là ở thông tin cũ tốt nhất.
cHao

6
@Aniket: Trong tất cả các công bằng, việc phân tách là dành riêng cho một JVM cụ thể trên một nền tảng cụ thể. Những người khác có thể dịch nó khác nhau. Trong thực tế, một số có thể sử dụng bảng băm cho một tra cứu. Nó vẫn không hoạt động tốt như một cú đánh bóng bàn, nhưng ít nhất nó có thể bị đóng. Nó chỉ mất nhiều thời gian hơn cho JIT và liên quan đến việc áp dụng thuật toán băm cho đầu vào. Vì vậy, mặc dù mã lắp ráp kết quả có thể được khai sáng, nhưng nó cũng không có thẩm quyền trừ khi bạn nói cụ thể về Hotspot v1.7.whwh trên Windows x86_64.
cHao

30

Câu trả lời nằm ở mã byte:

SwitchTest10.java

public class SwitchTest10 {

    public static void main(String[] args) {
        int n = 0;

        switcher(n);
    }

    public static void switcher(int n) {
        switch(n) {
            case 0: System.out.println(0);
                    break;

            case 1: System.out.println(1);
                    break;

            case 2: System.out.println(2);
                    break;

            case 3: System.out.println(3);
                    break;

            case 4: System.out.println(4);
                    break;

            case 5: System.out.println(5);
                    break;

            case 6: System.out.println(6);
                    break;

            case 7: System.out.println(7);
                    break;

            case 8: System.out.println(8);
                    break;

            case 9: System.out.println(9);
                    break;

            case 10: System.out.println(10);
                    break;

            default: System.out.println("test");
        }
    }       
}

Mã byte tương ứng; chỉ những phần có liên quan được hiển thị:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   tableswitch{ //0 to 10
        0: 60;
        1: 70;
        2: 80;
        3: 90;
        4: 100;
        5: 110;
        6: 120;
        7: 131;
        8: 142;
        9: 153;
        10: 164;
        default: 175 }

SwitchTest22.java:

public class SwitchTest22 {

    public static void main(String[] args) {
        int n = 0;

        switcher(n);
    }

    public static void switcher(int n) {
        switch(n) {
            case 0: System.out.println(0);
                    break;

            case 1: System.out.println(1);
                    break;

            case 2: System.out.println(2);
                    break;

            case 3: System.out.println(3);
                    break;

            case 4: System.out.println(4);
                    break;

            case 5: System.out.println(5);
                    break;

            case 6: System.out.println(6);
                    break;

            case 7: System.out.println(7);
                    break;

            case 8: System.out.println(8);
                    break;

            case 9: System.out.println(9);
                    break;

            case 100: System.out.println(10);
                    break;

            case 110: System.out.println(10);
                    break;
            case 120: System.out.println(10);
                    break;
            case 130: System.out.println(10);
                    break;
            case 140: System.out.println(10);
                    break;
            case 150: System.out.println(10);
                    break;
            case 160: System.out.println(10);
                    break;
            case 170: System.out.println(10);
                    break;
            case 180: System.out.println(10);
                    break;
            case 190: System.out.println(10);
                    break;
            case 200: System.out.println(10);
                    break;
            case 210: System.out.println(10);
                    break;

            case 220: System.out.println(10);
                    break;

            default: System.out.println("test");
        }
    }       
}

Mã byte tương ứng; một lần nữa, chỉ những phần có liên quan được hiển thị:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   lookupswitch{ //23
        0: 196;
        1: 206;
        2: 216;
        3: 226;
        4: 236;
        5: 246;
        6: 256;
        7: 267;
        8: 278;
        9: 289;
        100: 300;
        110: 311;
        120: 322;
        130: 333;
        140: 344;
        150: 355;
        160: 366;
        170: 377;
        180: 388;
        190: 399;
        200: 410;
        210: 421;
        220: 432;
        default: 443 }

Trong trường hợp đầu tiên, với phạm vi hẹp, mã byte được biên dịch sử dụng a tableswitch. Trong trường hợp thứ hai, mã byte được biên dịch sử dụng a lookupswitch.

Trong đó tableswitch, giá trị số nguyên trên đỉnh của ngăn xếp được sử dụng để lập chỉ mục vào bảng, để tìm mục tiêu nhánh / nhảy. Bước nhảy / nhánh này sau đó được thực hiện ngay lập tức. Do đó, đây là một O(1)hoạt động.

A lookupswitchphức tạp hơn. Trong trường hợp này, giá trị số nguyên cần được so sánh với tất cả các khóa trong bảng cho đến khi tìm thấy khóa chính xác. Sau khi tìm thấy khóa, mục tiêu rẽ nhánh / nhảy (mà phím này được ánh xạ tới) được sử dụng để nhảy. Bảng được sử dụng lookupswitchđược sắp xếp và thuật toán tìm kiếm nhị phân có thể được sử dụng để tìm khóa chính xác. Hiệu suất cho tìm kiếm nhị phân là O(log n), và toàn bộ quá trình cũng vậy O(log n), bởi vì bước nhảy vẫn còn O(1). Vì vậy, lý do hiệu suất thấp hơn trong trường hợp phạm vi thưa thớt là vì khóa chính xác trước tiên phải được tra cứu vì bạn không thể lập chỉ mục trực tiếp vào bảng.

Nếu có các giá trị thưa thớt và bạn chỉ có một tableswitchđể sử dụng, về cơ bản bảng sẽ chứa các mục giả cho đến defaulttùy chọn. Ví dụ, giả sử rằng ở mục cuối cùng SwitchTest10.java21thay vì 10, bạn nhận được:

public static void switcher(int);
  Code:
   0:   iload_0
   1:   tableswitch{ //0 to 21
        0: 104;
        1: 114;
        2: 124;
        3: 134;
        4: 144;
        5: 154;
        6: 164;
        7: 175;
        8: 186;
        9: 197;
        10: 219;
        11: 219;
        12: 219;
        13: 219;
        14: 219;
        15: 219;
        16: 219;
        17: 219;
        18: 219;
        19: 219;
        20: 219;
        21: 208;
        default: 219 }

Vì vậy, trình biên dịch về cơ bản tạo ra bảng khổng lồ này chứa các mục giả giữa các khoảng trống, chỉ đến mục tiêu nhánh của defaultlệnh. Ngay cả khi không có default, nó sẽ chứa các mục trỏ đến lệnh sau khối chuyển đổi. Tôi đã thực hiện một số thử nghiệm cơ bản và tôi thấy rằng nếu khoảng cách giữa chỉ số cuối cùng và chỉ số trước đó ( 9) lớn hơn 35, thì nó sử dụng lookupswitchthay cho a tableswitch.

Hành vi của switchcâu lệnh được định nghĩa trong Đặc tả máy ảo Java (§3.10) :

Trong trường hợp các công tắc chuyển đổi thưa thớt, biểu diễn bảng của hướng dẫn chuyển đổi bảng trở nên không hiệu quả về mặt không gian. Hướng dẫn tra cứu có thể được sử dụng thay thế. Lệnh lookupswitch ghép các khóa int (giá trị của nhãn trường hợp) với độ lệch đích trong một bảng. Khi một lệnh tra cứu được thực thi, giá trị của biểu thức của công tắc được so sánh với các phím trong bảng. Nếu một trong các khóa khớp với giá trị của biểu thức, việc thực thi sẽ tiếp tục ở phần bù mục tiêu được liên kết. Nếu không có khóa khớp, thực thi tiếp tục tại mục tiêu mặc định. [...]


1
Tôi đã hiểu từ câu hỏi rằng các số luôn luôn liền kề nhau nhưng phạm vi dài hơn hoặc ít hơn - tức là trong một ví dụ, các trường hợp đi từ 0 đến 5 trong khi trong một ví dụ khác, chúng đi từ 0 đến 30 - và không có ví dụ nào sử dụng các giá trị thưa thớt
assylias

@assylias Hmm, thú vị. Tôi đoán tôi đã hiểu nhầm câu hỏi. Hãy để tôi làm một số thử nghiệm thêm. Vì vậy, bạn đang nói rằng ngay cả với phạm vi liền kề từ 0-30, trình biên dịch sử dụng một lookupswitch?
Vivin Paliath

@VivinPaliath: Có, trong các thử nghiệm của tôi, các hằng số trường hợp luôn liền kề nhau, vì vậy về cơ bản tôi đang kiểm tra các công tắc trên [0, 1], [0, 1, 2], [0, 1, 2, 3] ... vv
Andrew Bissell

@VivinPaliath Không, mã byte luôn sử dụng một bảng dữ liệu - tuy nhiên trình biên dịch JIT dường như không biên dịch các bảng dữ liệu để lắp ráp theo cùng một cách tùy thuộc vào số lượng mục mà nó chứa.
assylias

6
@VivinPaliath Tôi chắc chắn có thể diễn đạt câu hỏi rõ ràng hơn. Tôi hơi khó hiểu khi đánh giá các câu trả lời liên quan đến công cụ lắp ráp và mã hóa cấp độ thấp này. Đối với tôi, dường như vẫn có sự phân biệt bảng / tra cứu thực sự quan trọng ở đây và câu trả lời của bạn là câu trả lời duy nhất cho đến nay (mặc dù các thuật ngữ khác có thể đặt ra cùng một khái niệm với các thuật ngữ khác nhau). Thêm vào đó tôi cũng thích có liên kết Spec JVM.
Andrew Bissell

19

Vì câu hỏi đã được trả lời (ít nhiều), đây là một số mẹo. Sử dụng

private static final double[] mul={1d, 10d...};
static double multiplyByPowerOfTen(final double d, final int exponent) {
      if (exponent<0 || exponent>=mul.length) throw new ParseException();//or just leave the IOOBE be
      return mul[exponent]*d;
}

Mã đó sử dụng IC (bộ đệm hướng dẫn) ít hơn đáng kể và sẽ luôn được nội tuyến. Mảng sẽ nằm trong bộ đệm dữ liệu L1 nếu mã nóng. Bảng tra cứu hầu như luôn luôn là một chiến thắng. (đặc biệt trên microbenchmark: D)

Chỉnh sửa: nếu bạn muốn phương thức được nội tuyến nóng, hãy xem xét các đường dẫn không nhanh throw new ParseException()như ngắn đến mức tối thiểu hoặc di chuyển chúng sang phương thức tĩnh (do đó làm cho chúng ngắn đến mức tối thiểu). Đó là throw new ParseException("Unhandled power of ten " + power, 0);một ý tưởng yếu b / c nó ăn rất nhiều ngân sách nội tuyến cho mã có thể được giải thích - nối chuỗi là khá dài dòng trong mã byte. Thêm thông tin và trường hợp thực tế w / ArrayList

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.