谷歌浏览器对做网站有什么好处,wordpress html5 win8,网站优化主要内容,微信生活门户网站源码问题
当我们有分支频率数据时#xff0c;有什么有趣的技巧可以做吗#xff1f;什么是条件移动#xff1f;
基础知识
如果您需要在来自一个分支的两个结果之间进行选择#xff0c;那么您可以在 ISA 级别做两件不同的事情。
首先#xff0c;你可以创建一个分支#xff…问题
当我们有分支频率数据时有什么有趣的技巧可以做吗什么是条件移动
基础知识
如果您需要在来自一个分支的两个结果之间进行选择那么您可以在 ISA 级别做两件不同的事情。
首先你可以创建一个分支 # %r (%rCond 1) ? $v1 : $v2cmp %rCond, $1jne Amov %r, $v1jmp EA: mov %r, $v2E:其次您可以执行依赖于比较结果的预测指令 。在 x86 中这采用条件移动 (CMOV) 的形式当选定条件成立时执行操作
# %r (%rCond 1) ? $v1 : $v2mov %r, $v1 ; put $v1 to %rcmp %rCond, ...cmovne %r, $v2 ; put $v2 to %r if condition is false执行条件移动的优点是它有时会生成更紧凑的代码就像在这个例子中一样并且它不会受到可能的分支预测错误惩罚。缺点是它需要在选择返回哪一边之前计算两边这可能会花费过多的 CPU 周期增加寄存器压力等。在分支情况下我们可以选择在检查条件后不计算内容。预测良好的分支将优于条件移动。
因此是否执行条件移动的选择在很大程度上取决于其成本预测。这就是分支分析可以帮助我们的地方它可以说出哪些分支可能没有被完美预测因此适合 CMOV 替换。当然 实际成本模型还包括我们正在处理的参数类型、两个计算分支的相对深度等。
实验
源码-用例1
Warmup(iterations 5, time 1, timeUnit TimeUnit.SECONDS)
Measurement(iterations 5, time 1, timeUnit TimeUnit.SECONDS)
Fork(1)
State(Scope.Benchmark)
BenchmarkMode(Mode.AverageTime)
OutputTimeUnit(TimeUnit.NANOSECONDS)
public class BranchFrequency {Benchmarkpublic void fair() {doCall(true);doCall(false);}CompilerControl(CompilerControl.Mode.DONT_INLINE)public int doCall(boolean condition) {if (condition) {return 1;} else {return 2;}}
}执行结果
我们在每次调用时都会在分支之间进行切换这意味着它的运行时配置文件在它们之间大约是 50%-50%。如果我们通过提供 -XX:ConditionalMoveLimit0 来限制条件移动替换那么我们就可以清楚地看到替换的发生。
# doCall, out of box variant4.36% ...4ac: mov $0x1,%r11d ; move $1 - %r113.24% ...4b2: mov $0x2,%eax ; move $2 - %res8.46% ...4b7: test %edx,%edx ; test boolean0.02% ...4b9: cmovne %r11d,%eax ; if false, move %r11 - %res7.88% ...4bd: add $0x10,%rsp ; exit the method8.12% ...4c1: pop %rbp18.60% ...4c2: cmp 0x340(%r15),%rsp...4c9: ja ...4d00.14% ...4cf: retq# doCall, CMOV conversion inhibited6.48% ...cac: test %edx,%edx ; test boolean╭ ...cae: je ...cc8│ ; if true...│ ...cb0: mov $0x1,%eax ; move $1 - %res7.41% │↗ ...cb5: add $0x10,%rsp ; exit the method0.02% ││ ...cb9: pop %rbp27.43% ││ ...cba: cmp 0x340(%r15),%rsp││ ...cc1: ja ...ccf3.28% ││ ...cc7: retq││ ; if false...7.04% ↘│ ...cc8: mov $0x2,%eax ; move $2 - %res0.02% ╰ ...ccd: jmp ...cb5 ; jump back在此示例中CMOV 版本的表现稍好一些
Benchmark Mode Cnt Score Error Units# Branches
BranchFrequency.fair avgt 25 5.422 ± 0.026 ns/op
BranchFrequency.fair:L1-dcache-loads avgt 5 12.078 ± 0.226 #/op
BranchFrequency.fair:L1-dcache-stores avgt 5 5.037 ± 0.120 #/op
BranchFrequency.fair:branch-misses avgt 5 0.001 ± 0.003 #/op
BranchFrequency.fair:branches avgt 5 10.037 ± 0.216 #/op
BranchFrequency.fair:cycles avgt 5 14.659 ± 0.285 #/op
BranchFrequency.fair:instructions avgt 5 35.184 ± 0.559 #/op# CMOVs
BranchFrequency.fair avgt 25 4.799 ± 0.094 ns/op
BranchFrequency.fair:L1-dcache-loads avgt 5 12.014 ± 0.329 #/op
BranchFrequency.fair:L1-dcache-stores avgt 5 5.005 ± 0.167 #/op
BranchFrequency.fair:branch-misses avgt 5 ≈ 10⁻⁴ #/op
BranchFrequency.fair:branches avgt 5 7.054 ± 0.118 #/op
BranchFrequency.fair:cycles avgt 5 12.964 ± 1.451 #/op
BranchFrequency.fair:instructions avgt 5 36.285 ± 0.713 #/op您可能认为这是因为 CMOV 没有分支预测失误惩罚但这种解释与计数器不一致。请注意在两种情况下“分支失误”几乎为零。这是因为硬件分支预测器实际上可以记住一个短暂的分支历史而这种反复出现的分支对它们来说没有任何问题。性能差异的实际原因是分支情况下的跳跃我们在关键路径上有一条额外的控制流指令。
源码-用例2
Warmup(iterations 5, time 500, timeUnit TimeUnit.MILLISECONDS)
Measurement(iterations 5, time 500, timeUnit TimeUnit.MILLISECONDS)
Fork(1)
BenchmarkMode(Mode.AverageTime)
OutputTimeUnit(TimeUnit.NANOSECONDS)
State(Scope.Thread)
public class AdjustableBranchFreq {Param(50)int percent;boolean[] arr;Setup(Level.Iteration)public void setup() {final int SIZE 100_000;final int Q 1_000_000;final int THRESH percent * Q / 100;arr new boolean[SIZE];ThreadLocalRandom current ThreadLocalRandom.current();for (int c 0; c SIZE; c) {arr[c] current.nextInt(Q) THRESH;}// Avoid uncommon traps on both branches.doCall(true);doCall(false);}Benchmarkpublic void test() {for (boolean cond : arr) {doCall(cond);}}CompilerControl(CompilerControl.Mode.DONT_INLINE)public int doCall(boolean condition) {if (condition) {return 1;} else {return 2;}}
}执行结果
使用不同的 percent 值和 -prof perfnorm JMH 分析器运行它将产生以下结果 依据上图你可以清楚地看到几件事
每个测试的分支数约为 5而 CMOV 转换将其降至 4。这与之前的反汇编转储相关我们将测试中的一个分支转换为 CMOV。另外 4 个分支来自测试基础设施本身。如果没有 CMOV分支测试性能会受到影响在 50% 的分支概率下会变得最差。这个峰值反映了硬件分支预测器几乎完全混乱因为它每次操作都会遇到大约 0.5 次分支失误。这意味着分支预测器并不是一直猜错这太荒谬了而只是一半的时间猜错。我推测基于历史的预测器会放弃让静态预测器选择最近的分支而我们只选择了一半的时间。使用 CMOV 后我们可以看到操作时间几乎持平 。该图表明 CMOV 成本模型对于此测试来说可能过于保守并且切换得有点晚。这并不一定意味着它有错误因为其他情况的表现很可能会有所不同。尽管如此当进行 CMOV 转换时对分支情况的改进是巨大的。您可能会注意到当分支预测准确率为 97% 时分支变体会低于 CMOV 中间平均值。当然这又是测试、硬件、虚拟机特有的事情。
总结
分支分析允许在执行概率敏感指令选择时做出或多或少明智的选择。条件移动替换通常使用分支频率信息来驱动替换。这再次强调了使用与真实数据类似的数据来预热 JIT 编译代码的必要性以便编译器能够针对特定情况进行有效优化。