Java 奇怪的分支性能
我的示例表明,当分支的概率为15%(或85%)而不是50%时,性能最差 有什么解释吗 代码太长,但相关部分如下:Java 奇怪的分支性能,java,performance,Java,Performance,我的示例表明,当分支的概率为15%(或85%)而不是50%时,性能最差 有什么解释吗 代码太长,但相关部分如下: private int diff(char c) { return TABLE[(145538857 * c) >>> 27] - c; } @Benchmark int timeBranching(int reps) { int result = 0; while (reps-->0) { for (final c
private int diff(char c) {
return TABLE[(145538857 * c) >>> 27] - c;
}
@Benchmark int timeBranching(int reps) {
int result = 0;
while (reps-->0) {
for (final char c : queries) {
if (diff(c) == 0) {
++result;
}
}
}
return result;
}
它统计给定字符串中的字符数。结果表明,当分支概率达到0.20左右时,时间会突然下降(性能提高)
更多关于下降的信息。显示发生了更多奇怪的事情。请注意,表示最小值和最大值的黑线非常短,除非靠近悬崖
它看起来像一个小的JIT错误。对于较小的分支概率,它会生成如下内容,只是由于展开而更加复杂(我简化了很多): 获取字符:
int r9d=querys[r10]
imul $0x8acbf29,%r9d,%ebx
乘法:ebx=145538857*r9d
shr $0x1b,%ebx
班次:ebx>>>=27
cmp %edx,%ebx
jae somewhere
检查边界:如果(ebx>edx | | ebx<0)转到某个地方
(并在那里抛出一个索引自动边界异常
)
cmp %r9d,%ebx
jne back
如果不相等,跳回循环开始:if(r9d!=ebx)返回
inc %eax
递增结果:eax++
jne back
简单地goto back
inc %eax
我们可以看到一件聪明和一件愚蠢的事情:
- 绑定检查通过一个单独的操作以最佳方式完成
- 它是完全冗余的,因为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
使用一条指令。这是一条多指令,但没有分支,因此没有分支预测失误惩罚
这解释了在20-80%范围内的非常恒定的时间。低于20%的斜率显然是由于分支预测失误
因此,使用大约0.04而不是0.18的适当阈值似乎是JIT失败
我无法摆脱所有性能问题都有一个连续的向下投票人的感觉。甚至其中两个问题。因此,请评论问题出了什么问题-假设你有话要说。我想我喜欢这个问题。我会对答案感兴趣。向下投票人,请解释原因。!!maaartinus可能是因为代码是l虽然是一个解释,但是读一读:这对每个CPU体系结构来说可能是不同的。非常有趣。但是我认为你的结论是JIT故障是不存在的-考虑你一遍遍地运行相同的方法,但是JIT可能会做出它的决策基础。d仅在预热阶段。由于基准测试的结构,您诱使JIT假设一个基于预热的分支比率,这并不能真正代表真实分布。至于数组边界检查是超级的,对于一个索引表达式来说,一般来说要证明它将保持在该范围内可能是一个非常困难的问题某些界限。@Durandal:但是热身阶段很长,当尝试不同的种子时,同样的事情会发生。如果它不具有代表性,JIT也会在另一个方向上出错,但它从未发生过。@Durandal:我同意一般来说这很难,但在这里它非常简单y==x>>27
意味着y<32
just-likey=z&&31
确实如此。对于实现者来说,这种转换可能还不够普遍,但它通常比掩蔽更好。我相信当它决定编译方法时(就JIT而言),预热阶段就结束了。除非它仍然从编译的代码中收集分支数据(我想你会在汇编代码中看到),它几乎会立即被编译,因为循环会使它非常快地跨过编译阈值(编辑:所以本质上我认为编译发生在第一次测试的分支比率期间).至于移位,你为什么说它比掩蔽更好?你是说在你的特定情况下还是在一般情况下?@Durandal:更复杂的是:有,并且通过周期性地对线程堆栈进行采样来进行分析,所以你在代码中看不到它。***下移位可以让你从乘法和乘法中获得最佳位乘法是最好的位扩展操作。在这种特殊情况下,我看不到任何使用掩蔽的比较快的解决方案。一般来说,我建议在从hashCode
计算索引时进行类似的处理。