有什么理由在MOV pc上使用BXR,除了ARMv7之前的拇指交互之外的R



Linux定义了一个汇编程序宏,以便在支持它的CPU上使用BX,这让我怀疑这是性能原因。

这个答案和Cortex-A7 MPCore技术参考手册也指出,它有助于分支预测。

然而,我的基准测试工作未能找到与ARM1176、Cortex-A17、CortexA72和Neoverse-N1-cpus的性能差异。

因此,在具有MMU并实现32位ARM指令集的cpu上,除了与Thumb代码交互之外,是否有任何理由更喜欢BX而不是MOV pc,

编辑以添加基准代码,全部对齐为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

在另一个寄存器上执行无用的计算,并使用BX:返回

div_bx2
mov  r9, #2
mul  r3, r9, lr
udiv r3, r3, r9
mul  r3, r9, r3
udiv r3, r3, r9
bx   lr

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

使用经典函数指针序列调用:

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}

使用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}

删除nop会使速度变慢。

结果以每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 

我测试的3个ARMv7处理器似乎将mov pc, lrbx lr都识别为返回指令。然而,带有ARM1176的Raspberry Pi 1被记录为具有返回预测,仅将BX lr和一些负载识别为返回指令,但我没有发现返回预测的证据。

header: .string "       Calle      BL       B  Difference"
format: .string "%12s %7i %7i %11in"
.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}

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

然而,在我的复盆子Pi1与运行Linux 5.4.51+的ARM1176复盆子Pi操作系统上,没有显示出可预测指令的优势:

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

如果您测试的是mov pc, ...总是跳到同一个返回地址的简单情况,那么常规的间接分支预测可能会很好。

我猜想bx lr可能使用一个返回地址预测器,该预测器假定匹配的call/ret(blx/bx lr(来正确地预测到各个呼叫站点的返回,而不会在正常的间接分支预测器中浪费空间。


要验证这一假设,请尝试类似的方法

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

如果我的假设是正确的,我预测mov pc, lr将比bx lr慢。

(可能需要更复杂的目标地址模式(在本例中为调用站点(来混淆某些CPU上的间接分支预测。一些CPU具有只能记住1个目标地址的间接分支预测器,但更复杂的预测器可以处理4个地址的简单重复模式。(


(这是的猜测,我对这些芯片都没有任何经验,但返回地址预测器的通用cpu架构技术是众所周知的,我读到它在多个ISAs上的实践中使用过。我确信x86会使用它:http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/调用/ret不匹配肯定是个问题。(

相关内容

  • 没有找到相关文章

最新更新