它看起来像是一个小的 JIT bug。对于较小的分支概率,它会生成如下所示的内容,只是由于展开而变得更加复杂(我简化了很多):
movzwl 0x16(%r8,%r10,2),%r9d
获取字符:int r9d = queries[r10]
imul $0x8acbf29,%r9d,%ebx
乘:ebx = 145538857 * r9d
shr $0x1b,%ebx
Shift: ebx >>>= 27
cmp %edx,%ebx
jae somewhere
检查边界:if (ebx > edx || ebx < 0) goto somewhere
(并在那里扔一个IndexOutOfBoundsException
.
cmp %r9d,%ebx
jne back
如果不相等则跳回循环开头:if (r9d != ebx) goto back
inc %eax
增加结果:eax++
jne back
Simply goto back
我们可以看到一件聪明的事情和一件愚蠢的事情:
- 绑定检查可以通过单个最佳方式完成无符号比较 http://www.aldeid.com/wiki/X86-assembly/Instructions/jae.
- 它完全是多余的,因为 x >>> 27 始终为正数并且小于表长度 (32)。
对于高于 20% 的分支概率,这三个指令
cmp %r9d,%ebx
jne back
inc %eax
被类似的东西取代
mov %eax,%ecx // ecx = result
inc %ecx // ecx++
cmp %r9d,%ebx // if (r9b == index)
cmoveq %ecx,%ebx // result = ecx
using a 有条件的移动 http://www.jaist.ac.jp/iscenter-new/mpc/altix/altixdata/opt/intel/vtune/doc/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc35.htm操作说明。这又是一条指令,但没有分支,因此没有分支错误预测惩罚。
这解释了 20-80% 范围内非常恒定的时间。低于 20% 的下降显然是由于分支预测错误造成的。
因此,看起来 JIT 未能使用大约 0.04 而不是 0.18 的正确阈值。