我有一些难以理解LookUpSwitch和TableSwitch在Java字节码。
如果我没有理解好,无论LookUpSwitch和TableSwitch对应switch
Java源代码的声明? 为什么一个JAVA语句产生2个不同的字节码?
每个茉莉文档:
- LookupSwitch
- tableswitch
我有一些难以理解LookUpSwitch和TableSwitch在Java字节码。
如果我没有理解好,无论LookUpSwitch和TableSwitch对应switch
Java源代码的声明? 为什么一个JAVA语句产生2个不同的字节码?
每个茉莉文档:
所不同的是,lookupswitch使用的钥匙和标签的表 ,但一个tableswitch使用表,只有标签 。
当执行tableswitch,堆栈顶部的int值被直接用作索引到表抢跳转目的地和立即执行跳转。 整个查找+跳跃过程是O(1)操作 ,这意味着它的速度极快。
当执行lookupswitch,直到发现匹配,然后旁边该键跳转目的地被用于执行跳跃在堆栈的顶部整型值与在表中的键进行比较。 由于lookupswitch表总是必须进行排序 ,使keyX <keyY每X <Y,整个查找+跳跃过程是O(log n)的操作为重点,将使用二进制搜索算法(它没有必要进行比较搜索对所有可能的密钥的int值找到匹配或确定没有按键的匹配)。 O(log n)的比O(1)略慢,但它仍然没有问题,因为许多已知的算法是O(log n)的,并且这些通常被认为是快; 甚至为O(n)或O(N * log n)的仍然被认为是一个相当不错的算法(慢/坏的算法有O(N ^ 2),O(N ^ 3),甚至更糟糕)。
该指令使用由基于事实的switch语句的紧凑程度,如编译器做出的决定
switch (inputValue) {
case 1: // ...
case 2: // ...
case 3: // ...
default: // ...
}
上面的开关是完全紧凑,它没有数字的“洞”。 编译器会创建这样一个tableswitch:
tableswitch 1 3
OneLabel
TwoLabel
ThreeLabel
default: DefaultLabel
从茉莉页面的伪代码解释了这个非常好:
int val = pop(); // pop an int from the stack
if (val < low || val > high) { // if its less than <low> or greater than <high>,
pc += default; // branch to default
} else { // otherwise
pc += table[val - low]; // branch to entry in table
}
此代码是这样的tableswitch是如何工作的很清楚。 val
是inputValue
, low
将是1(在交换机的最低值的情况下)和high
将是3(在开关最高情况下的值)。
即使有一些孔开关可以是紧凑的,如
switch (inputValue) {
case 1: // ...
case 3: // ...
case 4: // ...
case 5: // ...
default: // ...
}
上面的开关是“几乎紧凑”时,它仅具有单个孔。 编译器可能会产生以下指令:
tableswitch 1 6
OneLabel
FakeTwoLabel
ThreeLabel
FourLabel
FiveLabel
default: DefaultLabel
; <...code left out...>
FakeTwoLabel:
DefaultLabel:
; default code
正如你所看到的,编译器必须添加一个假的情况下2, FakeTwoLabel
。 由于2是开关的没有实际价值,FakeTwoLabel实际上是在改变码流的确切位置默认情况下位于,因为2的值应在事实上执行默认情况下的标签。
所以开关不必是完全紧凑的编译器创建一个tableswitch,但它至少应该是相当接近紧凑。 现在考虑下面的开关:
switch (inputValue) {
case 1: // ...
case 10: // ...
case 100: // ...
case 1000: // ...
default: // ...
}
该开关是隔靴搔痒紧凑,它已经超过百倍孔比的值 。 人们会称这是一个备用开关。 编译器必须产生近千假的情况下表达这种交换机作为tableswitch。 其结果将是一个巨大的表,极大地吹起来的类文件的大小。 这是不实际的。 相反,它会产生一个lookupswitch:
lookupswitch
1 : Label1
10 : Label10
100 : Label100
1000 : Label1000
default : DefaultLabel
此表只有5项,而不是逾千人。 该表具有4个实数值,为O(log 4)为2(登录在这里登录到的2基座顺便说一句,不为10的基础上,因为在计算机上的二进制数操作)。 这意味着它需要虚拟机最多两个比较以找到inputValue将标签或得出的结论,该值不在表中,因此必须要执行的默认值。 即使表有100项,它会采取VM最多7个比较,以找到正确的标签或决定要跳转到默认标签(7个比较小于100点的比较少了很多,你不觉得吗?)。
所以这是废话,这两个指令是可以互换的,或者对于两个指令的原因有历史原因的。 有两种不同的情况,一种为紧凑型值开关两个指令(最大速度)和一个用于备用值开关(不是最大速度,但还是不错的速度和非常紧凑的表格表示不顾一切数字孔)。
当究竟的javac编译1.8.0_45切换到任何一个?
要决定何时使用,你可以使用它, javac
选择算法为基础。
我们知道的源javac
是在langtools
回购。
然后,我们用grep:
hg grep -i tableswitch
和第一个结果是langtools / src目录/股/班/ COM /阳光/工具/ javac的/ JVM / Gen.java :
// Determine whether to issue a tableswitch or a lookupswitch
// instruction.
long table_space_cost = 4 + ((long) hi - lo + 1); // words
long table_time_cost = 3; // comparisons
long lookup_space_cost = 3 + 2 * (long) nlabels;
long lookup_time_cost = nlabels;
int opcode =
nlabels > 0 &&
table_space_cost + 3 * table_time_cost <=
lookup_space_cost + 3 * lookup_time_cost
?
tableswitch : lookupswitch;
哪里:
hi
:最大壳值 lo
:最小值的情况下 因此,我们得出结论,它考虑到的时间和空间复杂度,与3的时间复杂度的权重。
TODO我不明白为什么lookup_time_cost = nlabels
而不是log(nlabels)
因为tableswitch
可以在O进行(的log(n))与二进制搜索。
奖金:C ++实现还使一个O之间的类似的选择(1)跳表和O(长(n))的二进制搜索: 开关的优势if-else语句
Java虚拟机规范描述的差异。 “当开关的情况下,可以有效地表示为指数为目标偏移量的表,则使用tableswitch指令”。 该规范描述了更多的细节。
我怀疑它主要是历史,因为Java字节码强调的机器代码(例如Sun自己的CPU)的一些特异性结合。
所述tableswitch本质上是一个计算跳转,其中的目标是从查找表中获得。 上lookupswitch要求每个值的比较相反,基本上是一个迭代槽表元素,直到匹配的值被发现。
显然这些操作码是可互换的,但基于值,一个或另一个可以是更快或更紧凑的(例如比较在它们之间的大间隙和一个顺序组键设置键)。