Scala 2.11在相对密集的match
范围内将Int
表达式编译为lookupswitch
:
lookupswitch { // 21
-12: 200
-11: 200
-10: 184
-9: 190
-8: 190
-7: 190
-6: 190
-5: 190
-4: 200
-1: 200
2: 195
3: 195
4: 195
5: 195
6: 184
7: 184
12: 184
13: 184
18: 184
21: 184
25: 184
default: 180
}
Java 7将等效的switch
语句编译为tableswitch
:
tableswitch { // -12 to 25
-12: 168
-11: 168
-10: 177
-9: 174
-8: 174
-7: 174
-6: 174
-5: 174
-4: 168
-3: 185
-2: 185
-1: 168
0: 185
1: 185
2: 171
3: 171
4: 171
5: 171
6: 177
7: 177
8: 185
9: 185
10: 185
11: 185
12: 181
13: 181
14: 185
15: 185
16: 185
17: 185
18: 181
19: 185
20: 185
21: 181
22: 185
23: 185
24: 185
25: 181
default: 185
}
是否有某种方法可以强制Scala生成tableswitch
?
答案 0 :(得分:32)
您不应该关心字节码,因为现代JVM足够聪明,能够以同样有效的方式编译lookupswitch
和tableswitch
。
直觉tableswitch
应该更快,这也是建议的
JVM specification:
因此,tablewitch指令可能比空间考虑允许选择的lookupswitch更有效。
然而,该规范是在20多年前编写的,当时JVM没有JIT编译器。现代HotSpot JVM中是否存在性能差异?
package bench;
import org.openjdk.jmh.annotations.*;
@State(Scope.Benchmark)
public class SwitchBench {
@Param({"1", "2", "3", "4", "5", "6", "7", "8"})
int n;
@Benchmark
public long lookupSwitch() {
return Switch.lookupSwitch(n);
}
@Benchmark
public long tableSwitch() {
return Switch.tableSwitch(n);
}
}
为了精确控制字节码,我使用Jasmin构建了Switch
类。
.class public bench/Switch
.super java/lang/Object
.method public static lookupSwitch(I)I
.limit stack 1
iload_0
lookupswitch
1 : One
2 : Two
3 : Three
4 : Four
5 : Five
6 : Six
7 : Seven
default : Other
One:
bipush 11
ireturn
Two:
bipush 22
ireturn
Three:
bipush 33
ireturn
Four:
bipush 44
ireturn
Five:
bipush 55
ireturn
Six:
bipush 66
ireturn
Seven:
bipush 77
ireturn
Other:
bipush -1
ireturn
.end method
.method public static tableSwitch(I)I
.limit stack 1
iload_0
tableswitch 1
One
Two
Three
Four
Five
Six
Seven
default : Other
One:
bipush 11
ireturn
Two:
bipush 22
ireturn
Three:
bipush 33
ireturn
Four:
bipush 44
ireturn
Five:
bipush 55
ireturn
Six:
bipush 66
ireturn
Seven:
bipush 77
ireturn
Other:
bipush -1
ireturn
.end method
结果显示lookupswitch / tableswitch基准测试之间没有性能差异,但根据switch参数有一个小的变化:
让我们通过查看生成的汇编代码来验证理论
以下JVM选项将有所帮助:-XX:CompileCommand=print,bench.Switch::*
# {method} {0x0000000017498a48} 'lookupSwitch' '(I)I' in 'bench/Switch'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x000000000329b240: sub $0x18,%rsp
0x000000000329b247: mov %rbp,0x10(%rsp) ;*synchronization entry
; - bench.Switch::lookupSwitch@-1
0x000000000329b24c: cmp $0x4,%edx
0x000000000329b24f: je 0x000000000329b2a5
0x000000000329b251: cmp $0x4,%edx
0x000000000329b254: jg 0x000000000329b281
0x000000000329b256: cmp $0x2,%edx
0x000000000329b259: je 0x000000000329b27a
0x000000000329b25b: cmp $0x2,%edx
0x000000000329b25e: jg 0x000000000329b273
0x000000000329b260: cmp $0x1,%edx
0x000000000329b263: jne 0x000000000329b26c ;*lookupswitch
; - bench.Switch::lookupSwitch@1
...
我们在这里看到的是以中间值4开头的二元搜索(这解释了为什么案例4在上图中具有最佳性能)。
但有趣的是,tableSwitch
的编译方式完全相同!
# {method} {0x0000000017528b18} 'tableSwitch' '(I)I' in 'bench/Switch'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x000000000332c280: sub $0x18,%rsp
0x000000000332c287: mov %rbp,0x10(%rsp) ;*synchronization entry
; - bench.Switch::tableSwitch@-1
0x000000000332c28c: cmp $0x4,%edx
0x000000000332c28f: je 0x000000000332c2e5
0x000000000332c291: cmp $0x4,%edx
0x000000000332c294: jg 0x000000000332c2c1
0x000000000332c296: cmp $0x2,%edx
0x000000000332c299: je 0x000000000332c2ba
0x000000000332c29b: cmp $0x2,%edx
0x000000000332c29e: jg 0x000000000332c2b3
0x000000000332c2a0: cmp $0x1,%edx
0x000000000332c2a3: jne 0x000000000332c2ac ;*tableswitch
; - bench.Switch::tableSwitch@1
...
但是等等......为什么二元搜索,而不是跳转表?
HotSpot JVM具有启发式功能,仅为具有10+个案例的交换机生成跳转表。这可以通过选项-XX:MinJumpTableSize=
更改。
好的,让我们用更多标签扩展我们的测试用例并再次检查生成的代码。
# {method} {0x0000000017288a68} 'lookupSwitch' '(I)I' in 'bench/Switch'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x000000000307ecc0: sub $0x18,%rsp ; {no_reloc}
0x000000000307ecc7: mov %rbp,0x10(%rsp) ;*synchronization entry
; - bench.Switch::lookupSwitch@-1
0x000000000307eccc: mov %edx,%r10d
0x000000000307eccf: dec %r10d
0x000000000307ecd2: cmp $0xa,%r10d
0x000000000307ecd6: jb 0x000000000307ece9
0x000000000307ecd8: mov $0xffffffff,%eax
0x000000000307ecdd: add $0x10,%rsp
0x000000000307ece1: pop %rbp
0x000000000307ece2: test %eax,-0x1faece8(%rip) # 0x00000000010d0000
; {poll_return}
0x000000000307ece8: retq
0x000000000307ece9: movslq %edx,%r10
0x000000000307ecec: movabs $0x307ec60,%r11 ; {section_word}
0x000000000307ecf6: jmpq *-0x8(%r11,%r10,8) ;*lookupswitch
; - bench.Switch::lookupSwitch@1
^^^^^^^^^^^^^^^^^^^^^^^^^
是的!这是我们的计算跳转指令。请注意,这是为lookupswitch
生成的。但tableswitch
将会有完全相同的内容。
令人惊讶的是,HotSpot JVM甚至可以为具有间隙和异常值的交换机生成跳转表。 -XX:MaxJumpTableSparseness
控制差距的大小。例如。如果有1到10的标签,那么从13到20,最后一个标签值为99 - JIT将生成值99的保护测试,对于其余标签,它将创建一个表。
HotSpot源代码最终会说明在使用C2进行JIT编译的方法之后,lookupswitch
和tableswitch
之间不应存在性能差异。这基本上是因为解析两个指令最终都调用了适用于任意标签集的相同jump_switch_ranges
函数。
正如我们所看到的,HotSpot JVM可以使用二进制搜索编译tableswitch
,使用跳转表编译lookupswitch
,反之亦然。这取决于标签的数量和密度,但不取决于字节码本身。
所以,回答你原来的问题 - 你不需要!
答案 1 :(得分:2)
编辑:您可以通过注释匹配来检查表的创建或查找切换:
import scala.annotation.switch
(foo: @switch) match {
case 0 =>
case 1 =>
//And so forth
}
如果给定的匹配不能编译到表OR lookupswitch中,这将使它在编译时记录警告。