为什么Java在连续整数上的切换与添加的情况下运行得更快?

我正在研究一些需要高度优化的Java代码,因为它将在我的主程序逻辑中的许多点调用的热函数中运行。这段代码的一部分包括用double变量乘以任意非负的int exponents。获得相乘值的一个快速方法(编辑:但不是最快的,请参阅下面的更新2)是exponent上的switch:

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,因此在上面的代码段中实际上有19个case。由于我不确定我是否真的需要在case语句1018中完成所有10的幂,我运行了一些微基准测试,比较用这个switch语句完成1000万次操作的时间,以及用只有case0int0的switch(将int1限制为9或更少,以避免破坏削减后的switch)。我得到了一个相当令人惊讶的结果(至少对我来说!),更长的switch和更多的case语句实际上运行得更快。

我尝试添加更多返回虚拟值的__abc0,并发现在声明了大约22-27个__abc0时,我可以让开关运行得更快(尽管在代码运行时,这些虚拟情况从未真正发生)。(同样,case是通过将前面的case常量加1来连续添加的。)这些执行时间差异不是很显著:对于010之间的随机exponent,虚拟填充switch语句在1.49秒内完成1000万次执行,而非填充版本的执行时间为1.54秒,每次执行总共节省了5ns。因此,从优化的角度来看,这种事情并不值得为填充switch语句而付出努力。但我仍然觉得奇怪和违反直觉的是,当更多的__abc0被添加到switch中时,switch的执行速度并没有变慢(或者最好保持不变的case2时间)。

switch benchmarking results

这些是我通过运行随机生成的exponent值的各种限制获得的结果。我没有包括一直到1exponent限制的结果,但曲线的一般形状保持不变,在12-17格标记附近有一个山脊,在18-28格标记之间有一个山谷。所有测试都在junitbenchmark中运行,使用随机值的共享容器以确保相同的测试输入。我还按照从最长的switch语句到最短的顺序运行测试,反之亦然,以尝试并消除与排序相关的测试问题的可能性。我已经把我的测试代码放在一个github回购,如果有人想尝试重现这些结果。

那么,这里发生了什么?我的架构或微基准构建的一些古怪之处?或者Java switch1828 case范围内执行的速度真的比从1117快一点吗?

github test repo "switch-experiment"

更新:我清理了相当多的基准测试库,并在/results中添加了一个文本文件,在更广泛的可能exponent值范围内输出一些结果。我还在测试代码中添加了一个选项,不从default抛出Exception,但这似乎不会影响结果。

在2009年的xkcd论坛上找到了一些关于这个问题的很好的讨论:http://forums.xkcd.com/viewtopic.php?f=11&t=33524。OP中关于使用Array.binarySearch()的讨论让我想到了上面的求幂模式的一个简单的基于数组的实现。没有必要进行二进制搜索,因为我知道array中的条目是什么。它似乎比使用switch快3倍,显然是以牺牲switch提供的一些控制流为代价的。该代码已添加到github回购也。

15288 次浏览

开关情况是更快,如果情况值被放置在一个狭窄的范围。

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

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

如wikipedia中关于编译部分中的switch语句所述

如果输入值的范围可识别为“小”,并且只有a 很少有差距,一些编译器合并了一个优化器可能实际上 的分支表或数组实现switch语句 索引函数指针,而不是一长串的条件 指令。这允许switch语句立即进行判断 执行什么分支,而不必通过列表 比较。< / p >

正如通过另一个答案所指出的,因为case值是连续的(而不是稀疏的),为各种测试生成的字节码使用一个切换表(字节码指令tableswitch)。

然而,一旦JIT开始工作并将字节码编译为程序集,tableswitch指令并不总是生成指针数组:有时切换表会转换为类似lookupswitch的结构(类似于if/else if结构)。

反编译JIT生成的程序集(热点JDK 1.7)表明,当有17个或更少的情况时,它使用一系列if/else if,当有18个以上的情况时(更有效)使用一个指针数组。

使用18这个神奇数字的原因似乎归结于MinJumpTableSize JVM标志的默认值(代码中的第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个case的程序集(如下)看起来与18个case相似,除了代码中间出现的额外movapd xmm0,xmm1@cHao发现 -然而性能下降的最可能原因是方法太长,无法与默认的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

答案就在字节码中:

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)操作。

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

如果有稀疏值,并且只有tableswitch可以使用,表本质上包含指向default选项的虚拟项。例如,假设SwitchTest10.java中的最后一项是21而不是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指令的分支目标。即使没有default,它也会包含指向开关块中的指令的条目。我做了一些基本的测试,我发现如果最后一个索引和前一个索引(9)之间的差距大于35,它使用lookupswitch而不是tableswitch

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

当开关的情况很稀疏时,表witch指令的表表示在空间方面变得低效。可以用查找开关指令代替。查找开关指令将int键(大小写标签的值)与表中的目标偏移量配对。当执行lookupswitch指令时,switch表达式的值将与表中的键进行比较。如果其中一个键与表达式的值匹配,则在相关的目标偏移量处继续执行。如果没有匹配的键,则继续执行默认目标。[…]

既然问题已经回答了(或多或少),这里有一些提示。 使用< / p >
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,它消耗了大量可以解释的代码的内联预算-字符串连接在字节码中相当冗长。更多信息和实际情况w/ ArrayList

基于javac源,你可以用一种使用tableswitch的方式来编写switch。

我们可以使用javac源代码中的计算来计算第二个示例的成本。

lo = 0
hi = 220
nlabels = 24


table_space_cost = 4 + hi - lo + 1
table_time_cost = 3
lookup_space_cost = 3 + 2 * nlabels
lookup_time_cost = nlabels


table_cost = table_space_cost + 3 * table_time_cost // 234
lookup_cost = lookup_space_cost + 3 * lookup_time_cos // 123

这里表开关的开销(234)比查找开关的开销(123)高,因此查找开关将被选为这个switch语句的操作码。