ARMv8平台分支目标地址对齐为何反而导致性能下降?
我有一段用于计算三个向量求和的Go代码:
for i := 0; i < 10000; i++ { c[i] += a[i] + b[i] }
在ARMv8系统上,这段代码生成的汇编如下:
fcddc: f86478c5 ldr x5, [x6, x4, lsl #3] fcde0: f8647807 ldr x7, [x0, x4, lsl #3] fcde4: f8647868 ldr x8, [x3, x4, lsl #3] fcde8: 8b070107 add x7, x8, x7 fcdec: 8b0500e5 add x5, x7, x5 fcdf0: f82478c5 str x5, [x6, x4, lsl #3] fcdf4: 91000484 add x4, x4, #0x1 fcdf8: eb04003f cmp x1, x4 fcdfc: 54ffff0c b.gt fcddc
可以看到循环体起始于未对齐地址fcddc。根据ARM性能手册,我尝试添加NOP指令将其对齐,修改后的汇编如下:
fcdec: d503201f nop fcdf0: d503201f nop fcdf4: d503201f nop fcdf8: d503201f nop fcdfc: d503201f nop fce00: f86478c5 ldr x5, [x6, x4, lsl #3] fce04: f8647807 ldr x7, [x0, x4, lsl #3] fce08: f8647868 ldr x8, [x3, x4, lsl #3] fce0c: 8b070107 add x7, x8, x7 fce10: 8b0500e5 add x5, x7, x5 fce14: f82478c5 str x5, [x6, x4, lsl #3] fce18: 91000484 add x4, x4, #0x1 fce1c: eb04003f cmp x1, x4 fce20: 54ffff0c b.gt fce00
理论上对齐应提升性能,但实际却出现大幅性能下降。使用perf工具进行自顶向下分析未得到有效结果,仅发现对齐版本存在更多后端绑定停顿,请问该现象的原因是什么?
出现这种反直觉现象的核心原因如下:
指令缓存空间挤占与预取效率下降
添加的5条NOP指令挤占了L1指令缓存的有限空间,原本仅36字节的循环体可以和周围代码共享缓存行,对齐后整体占用空间变大,可能导致循环体无法稳定驻留在L1指令缓存中,或者预取器需要频繁跨缓存行取指,反而降低了指令供应效率,引发后端执行单元等待。分支跳转距离变化引发的额外开销
原始循环的分支跳转距离为-20字节,属于短距离向后跳转,ARM分支预测器对这类跳转的预测准确率和响应速度极高。对齐后跳转距离变为-32字节,部分ARM微架构(如Cortex-A53/A72)对稍长的循环跳转需要额外的预测周期,甚至会出现短暂的预取中断,直接导致后端执行停顿增加。小循环对齐的收益不足以抵消开销
ARM性能手册中的对齐建议通常针对大循环(迭代次数多、循环体指令量大),此时对齐带来的译码、预取收益能覆盖相关开销。但你的循环仅10000次迭代,且循环体极小,对齐带来的微乎其微的收益完全被地址变化引发的分支、预取额外开销抵消,甚至反超。编译器指令调度的隐性破坏
编译器生成原始汇编时,已经针对目标ARM微架构做了指令调度优化(比如将三条加载指令连续排布,最大化执行单元并行性)。手动移动循环体地址可能破坏了指令与执行单元端口的绑定关系,导致原本可以并行执行的加载、加法指令出现调度冲突,进而增加后端执行停顿。
内容的提问来源于stack exchange,提问作者alexanius

