Tim*_*win 8 assembly arm cpu-architecture branch-prediction micro-architecture
Linux 定义了一个BX
在支持它的 CPU上使用的汇编宏,这让我怀疑有一些性能原因。
这个答案和Cortex-A7 MPCore 技术参考手册也指出它有助于分支预测。
然而,我的基准测试工作未能发现与 ARM1176、Cortex-A17、Cortex-A72 和 Neoverse-N1 cpu 的性能差异。
有没有因此任何理由,更喜欢BX
过MOV pc,
上了MMU的CPU,并实现了32位ARM指令集,比Thumb代码交互等?
编辑添加基准代码,全部对齐到 64 字节:
执行无用的计算lr
并使用返回BX
:
div_bx
mov r9, #2
mul lr, r9, lr
udiv lr, lr, r9
mul lr, r9, lr
udiv lr, lr, r9
bx lr
Run Code Online (Sandbox Code Playgroud)
在另一个寄存器上执行无用的计算并使用BX
以下方法返回:
div_bx2
mov r9, #2
mul r3, r9, lr
udiv r3, r3, r9
mul r3, r9, r3
udiv r3, r3, r9
bx lr
Run Code Online (Sandbox Code Playgroud)
执行无用的计算lr
并使用返回MOV
:
div_mov
mov r9, #2
mul lr, r9, lr
udiv lr, lr, r9
mul lr, r9, lr
udiv lr, lr, r9
mov pc, lr
Run Code Online (Sandbox Code Playgroud)
使用经典函数指针序列调用:
movmov
push {lr}
loop mov lr, pc
mov pc, r1
mov lr, pc
mov pc, r1
mov lr, pc
mov pc, r1
mov lr, pc
mov pc, r1
subs r0, r0, #1
bne loop
pop {pc}
Run Code Online (Sandbox Code Playgroud)
调用使用BLX
:
blx
push {lr}
loop nop
blx r1
nop
blx r1
nop
blx r1
nop
blx r1
subs r0, r0, #1
bne loop
pop {pc}
Run Code Online (Sandbox Code Playgroud)
删除nop
s make 的速度较慢。
结果为每 100000000 次循环的秒数:
Neoverse-N1 r3p1 (AWS c6g.medium)
mov+mov blx
div_bx 5.73 1.70
div_mov 5.89 1.71
div_bx2 2.81 1.69
Cortex-A72 r0p3 (AWS a1.medium)
mov+mov blx
div_bx 5.32 1.63
div_mov 5.39 1.58
div_bx2 2.79 1.63
Cortex-A17 r0p1 (ASUS C100P)
mov+mov blx
div_bx 12.52 5.69
div_mov 12.52 5.75
div_bx2 5.51 5.56
Run Code Online (Sandbox Code Playgroud)
我测试的 3 个 ARMv7 处理器似乎同时识别mov pc, lr
和bx lr
作为返回指令。然而,带有 ARM1176的Raspberry Pi 1被记录为具有仅将BX lr
某些负载识别为返回指令的返回预测,但我没有发现返回预测的证据。
header: .string " Calle BL B Difference"
format: .string "%12s %7i %7i %11i\n"
.align
.global main
main: push {r3-r5, lr}
adr r0, header
bl puts
@ Warm up
bl clock
mov r0, #0x40000000
1: subs r0, r0, #1
bne 1b
bl clock
.macro run_test test
2: bl 1f
nop
bl clock
mov r4, r0
ldr r0, =10000000
.balign 64
3: mov lr, pc
bl 1f
nop
mov lr, pc
bl 1f
nop
mov lr, pc
bl 1f
nop
subs r0, r0, #1
bne 3b
bl clock
mov r5, r0
ldr r0, =10000000
.balign 64
5: mov lr, pc
b 1f
nop
mov lr, pc
b 1f
nop
mov lr, pc
b 1f
nop
subs r0, r0, #1
bne 5b
bl clock
sub r2, r5, r4
sub r3, r0, r5
sub r0, r3, r2
str r0, [sp]
adr r1, 4f
ldr r0, =format
bl printf
b 2f
.ltorg
4: .string "\test"
.balign 64
1:
.endm
run_test mov
mov lr, lr
mov pc, lr
run_test bx
mov lr, lr
bx lr
run_test mov_mov
mov r2, lr
mov pc, r2
run_test mov_bx
mov r2, lr
bx r2
run_test pp_mov_mov
push {r1-r11, lr}
pop {r1-r11, lr}
mov r12, lr
mov pc, r12
run_test pp_mov_bx
push {r1-r11, lr}
pop {r1-r11, lr}
mov r12, lr
bx r12
run_test pp_mov_mov_f
push {r0-r11}
pop {r0-r11}
mov r12, lr
mov pc, r12
run_test pp_mov_bx_f
push {r0-r11}
pop {r0-r11}
mov r12, lr
bx r12
run_test pp_mov
push {r1-r11, lr}
pop {r1-r11, lr}
mov r12, lr
mov pc, lr
run_test pp_bx
push {r1-r11, lr}
pop {r1-r11, lr}
mov r12, lr
bx lr
run_test pp_mov_f
push {r0-r11}
pop {r0-r11}
mov r12, lr
bx lr
run_test pp_bx_f
push {r0-r11}
pop {r0-r11}
mov r12, lr
bx lr
run_test add_mov
nop
add r2, lr, #4
mov pc, r2
run_test add_bx
nop
add r2, lr, #4
bx r2
2: pop {r3-r5, pc}
Run Code Online (Sandbox Code Playgroud)
Cortex-A17 的结果符合预期:
Calle BL B Difference
mov 94492 255882 161390
bx 94673 255752 161079
mov_mov 255872 255806 -66
mov_bx 255902 255796 -106
pp_mov_mov 506079 506132 53
pp_mov_bx 506108 506262 154
pp_mov_mov_f 439339 439436 97
pp_mov_bx_f 439437 439776 339
pp_mov 247941 495527 247586
pp_bx 247891 494873 246982
pp_mov_f 230846 422626 191780
pp_bx_f 230850 422772 191922
add_mov 255997 255896 -101
add_bx 255900 256288 388
Run Code Online (Sandbox Code Playgroud)
然而,在我的 Raspberry Pi1 上,ARM1176 运行来自 Raspbery Pi OS 的 Linux 5.4.51+,没有显示可预测指令的优势:
Calle BL B Difference
mov 464367 464372 5
bx 464343 465104 761
mov_mov 464346 464417 71
mov_bx 464280 464577 297
pp_mov_mov 1073684 1074169 485
pp_mov_bx 1074009 1073832 -177
pp_mov_mov_f 769160 768757 -403
pp_mov_bx_f 769354 769368 14
pp_mov 885585 1030520 144935
pp_bx 885222 1032396 147174
pp_mov_f 682139 726129 43990
pp_bx_f 682431 725210 42779
add_mov 494061 493306 -755
add_bx 494080 493093 -987
Run Code Online (Sandbox Code Playgroud)
如果您正在测试总是跳转到相同返回地址的简单情况mov pc, ...
,则常规间接分支预测可能会很好。
我猜想bx lr
可能会使用返回地址预测器,该预测器假设匹配 call/ret ( blx
/ bx lr
) 来正确预测到各个调用站点的返回,并且不会浪费正常间接分支预测器中的空间。
根据 Timothy 对 Cortex-A17、Cortex-A72 和 Neoverse-N1 的测试,在这些 CPU 上mov pc, lr
被识别为可以与blx
. 所以这个答案中的猜测对于那些 CPU 来说似乎是错误的。
为了检验这个假设,尝试类似的事情
testfunc:
bx lr @ or mov pc,lr
caller:
mov r0, #100000000
.p2align 4
.loop:
blx testfunc
blx testfunc # different return address than the previous blx
blx testfunc
blx testfunc
subs r0, #1
bne .loop
Run Code Online (Sandbox Code Playgroud)
如果我的假设是正确的,我预测这mov pc, lr
会比 慢bx lr
。
(可能需要更复杂的目标地址模式(在本例中为调用点)来混淆某些 CPU 上的间接分支预测。某些 CPU 具有只能记住 1 个目标地址的间接分支预测器,但更复杂的预测器可以处理简单的目标地址。 4 个地址的重复模式。)
(这是一个猜测,我对这些芯片没有任何经验,但返回地址预测器的通用 cpu 架构技术是众所周知的,并且我读到它在多个 ISA 上实际使用。我确信 x86 使用它: http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/ 不匹配的调用/ret 肯定是一个问题。)