为什么在添加了大小写的情况下,Java开启连续int似乎运行得更快?


276

我正在研究一些Java代码,这些代码需要高度优化,因为它将在热门函数中运行,这些热点函数在我的主程序逻辑中的许多地方被调用。该代码的一部分涉及将double变量乘以10任意非负数int exponents。获得乘数的一种快速方法(编辑:但不是最快的方法,请参见下面的Update 2)是在switchexponent

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

上面带注释的省略号表示case int常数继续增加1,因此case上面的代码片段中确实有19 s。因为我不知道我是否真的需要10一切权力,并case声明1018,我跑了一些微基准与此比较的时间完成10万次switch声明与一个switch只有caseš 09(与exponent限定的9个或更少避免破坏缩减的switch)。我得到的结果令人惊讶(至少对我来说是这样!),switch带有更多case语句的时间越长,实际上运行得越快。

百思不得其解,我尝试添加更多的cases,这些s返回了伪值,并且发现我可以使开关在声明的cases为22-27 的情况下运行得更快(即使这些伪造的情况在代码运行时从未真正被击中) )。(再次,case通过将先验case常数增加s以连续方式添加s 1。)这些执行时间差异不是很明显:对于和exponent之间的随机性,伪填充语句在1.49秒内完成了1000万次执行,而未填充语句则完成了1.54秒版本,每次执行总共节省5ns。因此,不是那种痴迷于填充010switchswitch从优化的角度来看,此声明值得您付出努力。但是我仍然发现,添加更多s switch不会使a 变慢(或者最好保持恒定的O(1)时间),这只是好奇和反直觉case

切换基准测试结果

这些是我通过对随机生成的exponent值进行各种限制而获得的结果。我不包括结果到一路1exponent限制,但曲线的总体形状相同,大约有12-17的情况下标记的脊和18-28之间的山谷。使用共享容器的随机值在JUnitBenchmarks中运行所有测试,以确保测试输入相同。我还按照从最长的switch陈述到最短的陈述来进行测试,反之亦然,以试图消除与排序有关的测试问题的可能性。如果有人想重现这些结果,我会将测试代码放在github仓库中。

那么,这是怎么回事?我的建筑或微基准建筑有些变化吗?或者是Java switch真的快一点在执行1828 case的范围比它从11最高17

github测试仓库“ switch-experiment”

更新:我清理了很多基准测试库,并在/ results中添加了一个文本文件,并在更大范围的可能exponent值中提供了一些输出。我还在测试代码中添加了一个选项,不要引发Exceptionfrom default,但这似乎不会影响结果。

更新2:在2009年的xkcd论坛上,找到了关于此问题的很好的讨论:http ://forums.xkcd.com/viewtopic.php?f=11&t=33524 。OP对使用的讨论为Array.binarySearch()我提供了一个简单的基于阵列的上述幂模式实现的想法。不需要二进制搜索,因为我知道其中的条目array是什么。它的运行速度似乎比使用快3倍switch,显然是以牺牲一些控制流switch为代价的。该代码也已添加到github仓库中。


64
现在,各地的所有Google员工在所有switch陈述中都将恰好有22个案例,因为这显然是最佳的解决方案。:D(请不要显示给我的线索。)
asteri

2
您有更简单的SSCCE吗?这个不适合我编译。尽管我在Java性能方面表现不佳,但我还是想尝试一下。
Mysticial

5
您可能会在我有关基于字符串的情况的答案中找到“ JVM中的开关”部分,这很有帮助。我认为这里正在发生的事情是您正在从切换lookupswitchtableswitch。反汇编您的代码javap肯定会告诉您。
埃里克森

2
我将依赖项jar添加到了仓库中的/ lib文件夹中。@Mysticial抱歉,我已经花了太多时间沿着这个兔子洞走了!如果您从测试类中删除“ extends AbstractBenchmark”并摆脱了“ com.carrotsearch”导入,则可以仅使用JUnit依赖项运行,但是胡萝卜搜索功能对于过滤掉JIT的某些噪声非常有用。和热身期。不幸的是,我不知道如何在IntelliJ之外运行这些JUnit测试。
Andrew Bissell 2013年

2
@AndrewBissell我设法用一个简单得多的基准再现您的结果。对于中小型性能而言,分支与表之间的关系有些明显。但是,我没有比其他任何人更好的洞察力了,这涉及到30例案件……
Mysticial 2013年

Answers:


228

正如另一个答案所指出的那样,由于case值是连续的(而不是稀疏的),因此您为各种测试生成的字节码使用了一个交换表(bytecode指令tableswitch)。

但是,一旦JIT开始工作并将字节码编译为汇编代码,该tableswitch指令就不会总是产生一个指针数组:有时,切换表会转换为看起来像a lookupswitch(类似于if/ else if结构)的表。

对由JIT(热点JDK 1.7)生成的程序集进行反编译显示,如果在17个案例以下的情况下,它将使用一系列if / else,而在18个案例以下的情况下将使用一个指针数组(效率更高)。

使用此神奇数字18的原因似乎可以归结为MinJumpTableSizeJVM标志的默认值(在代码中的第352行附近)。

我已经在热点编译器列表中提出了这个问题,这似乎是过去测试的遗留问题。请注意,执行更多基准测试后,此默认值已在JDK 8中删除

最后,当方法变得太长时(在我的测试中为25种以上的情况),该方法不再使用默认的JVM设置进行内联-这是此时性能下降的最可能原因。


在5种情况下,反编译后的代码如下所示(注意cmp / je / jg / jmp指令,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    

在18种情况下,程序集如下所示(注意使用了指针数组,并取消了所有比较的需要:jmp QWORD PTR [r8+r10*1]直接跳转到正确的乘法)-这可能是性能提高的原因:

[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    

最后,具有30种情况(下)的程序集看起来类似于18种情况,除了@cHao发现的movapd xmm0,xmm1,出现在代码中间的其他情况之外-但是性能下降的最可能原因是该方法也可以内联默认的JVM设置:

[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说实话,我也不明白任何细节;-)
assylias 2013年

4
+1很好的答案!您能否分解30多个案例,以比较性能是否超出OP图表中的“下降”?
asteri 2013年


2
@AndrewBissell我的猜测是,不同的行为基于以下两种情况之一:(i)跨体系结构性能测试,该测试表明,仅当案例数大于18时,指针数组才有效;或者(ii)将代码分析为它会运行,并且探查器确定运行时哪种方法更好。我找不到答案。
assylias 2013年

3
30箱拆卸和18箱拆卸看起来基本相同。差异似乎主要限于大约第11种情况之后的额外寄存器改组。不能说为什么JITter那样做;似乎没有必要。
cHao 2013年

46

如果将大小写值放在狭窄的范围内,例如,切换-大小写会更快。

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

因为在这种情况下,编译器可以避免对switch语句中的每个案例分支执行比较。编译器生成一个跳转表,其中包含要在不同分支上执行的操作的地址。操作执行开关的值,将其转换为in的索引jump table。在此实现中,switch语句中花费的时间比等效的if-else-if语句级联中花费的时间少得多。同样,switch语句中花费的时间与switch语句中案例分支的数量无关。

如Wikipedia中有关“ 编译”部分中的switch语句所述。

如果输入值的范围可以确定地是“很小的”并且只有几个空隙,那么一些包含优化器的编译器实际上可以将switch语句实现为分支表或索引函数指针数组,而不是冗长的条件指令序列。这使switch语句可以立即确定要执行的分支,而不必遍历比较列表。


4
那是不对的。不管情况值是窄还是宽,它都会更快。它是O(1)-大小写值之间的距离无关紧要。
Aniket Inge

6
@Aniket:阅读维基百科的这篇文章。en.wikipedia.org/wiki/Branch_table
Vishal K

14
@Aniket:如果范围宽且稀疏,则不是O(1)。有两种开关,如果范围过于分散,Java会将其编译为“ lookupswitch”而不是“ tableswitch”。前者需要对每个分支进行比较直到找到一个分支,而后者则不需要。
cHao 2013年

4
维基百科是查找参考文献的好地方,但不应视为权威来源。您在此处阅读的任何内容最多都是二手信息。
cHao 2013年

6
@Aniket:公平地说,反汇编是特定于特定平台上的给定JVM的。其他人可能会有所不同。实际上,有些人可能将哈希表用于lookupswitch。它仍然不能像tableswitch那样好用,但是至少可以接近。JIT将花费更长的时间,并涉及对输入应用哈希算法。因此,尽管最终的汇编代码可以启发人,但也没有权威性,除非您专门谈论Windows x86_64上的Hotspot v1.7。
cHao 2013年

30

答案在于字节码:

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

对应的字节码;仅显示相关部分:

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

对应的字节码;同样,仅显示相关部分:

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 }

在第一种情况下,范围较窄,编译后的字节码使用tableswitch。在第二种情况下,编译后的字节码使用lookupswitch

在中tableswitch,堆栈顶部的整数值用于索引表,以查找分支/跳转目标。然后立即执行此跳转/分支。因此,这是一个O(1)操作。

A lookupswitch更复杂。在这种情况下,需要将整数值与表中的所有键进行比较,直到找到正确的键为止。找到键后,将使用分支/跳转目标(此键映射到该目标)进行跳转。使用的表lookupswitch已排序,并且可以使用二进制搜索算法来找到正确的密钥。二分搜索的性能很高O(log n),而且整个过程也如此O(log n),因为跳转仍然是静止的O(1)。因此,在稀疏范围的情况下性能较低的原因是必须先查找正确的键,因为您无法直接索引到表中。

如果存在稀疏值,并且只能tableswitch使用,则表实际上将包含指向该default选项的虚拟条目。例如,假设最后一个条目SwitchTest10.java21而不是10,您将得到:

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 }

因此,编译器基本上会创建一个巨大的表,其中包含间隙之间的伪条目,指向default指令的分支目标。即使没有a default,它也会在switch块之后包含指向该指令的条目。我进行了一些基本测试,发现如果上一个索引与上一个索引(9)之间的差距大于35,则使用a lookupswitch而不是a tableswitch

switch语句的行为在Java虚拟机规范(§3.10)中定义

如果switch的情况很少,则tableswitch指令的表表示形式在空间方面变得效率低下。可以改用lookupswitch指令。lookupswitch指令将int键(大小写标签的值)与表中的目标偏移量配对。当执行lookupswitch指令时,会将switch表达式的值与表中的键进行比较。如果键之一与表达式的值匹配,则在关联的目标偏移量处继续执行。如果没有键匹配,则在默认目标处继续执行。[...]


1
我从这个问题中了解到,数字始终是连续的,但是范围或多或少是长的-例如,在一个示例中,情况从0到5,而在另一个示例中,情况从0到30-并且没有一个示例使用稀疏值
assylias 2013年

@assylias嗯,很有趣。我想我误解了这个问题。让我做更多的实验。因此,您说的是即使在0 到30 的连续范围内,编译器也会使用lookupswitch?。
Vivin Paliath

@VivinPaliath:是的,在我的测试中,情况常量始终是连续的,因此我基本上是在[0,1],[0、1、2],[0、1、2、3] ...等上测试开关
Andrew Bissell 2013年

@VivinPaliath不,字节码始终使用一个tableswitch-但是,JIT编译器似乎并没有按照相同的方式将tableswitch编译为汇编程序,具体取决于它包含多少项。
assylias 2013年

6
@VivinPaliath我可以肯定地说出这个问题。在评估涉及此低级字节码和汇编程序的答案时,我有点不擅长。在我看来,tableswitch / lookupswitch的区别在这里实际上很重要,而到目前为止,您的答案是唯一使用这些术语的答案(尽管其他人可能会使用不同的术语提出相同的概念)。另外,我也喜欢具有JVM Spec链接。
Andrew Bissell 2013年

19

由于已经(或多或少)回答了问题,因此这里有一些提示。用

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

该代码使用的IC(指令缓存)少得多,并且将始终内联。如果代码很热,该阵列将位于L1数据缓存中。查找表几乎总是胜利。(尤其是在微基准测试上:D)

编辑:如果您希望该方法是热内联的,请考虑将非快速路径throw new ParseException()设为尽可能短,或者将其移动到单独的静态方法中(因此将它们设为最小)。这是throw new ParseException("Unhandled power of ten " + power, 0);一个弱的想法B / C它吃的代码有很多的内联的预算可以只解释-字符串连接是相当冗长的字节码。更多信息和带有ArrayList真实案例

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.