assembly 除了ARMv7之前的拇指互通之外,还有什么理由使用BX R而不是MOV PC,R?

eufgjt7s  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(73)

Linux defines an assembler macro在支持它的CPU上使用BX,这让我怀疑有一些性能原因。
This answerCortex-A7 MPCore Technical Reference Manual还声明它有助于分支预测。
然而,我的基准测试工作未能找到ARM 1176,Cortex-A17,Cortex-A72和Neoverse-N1 CPU的性能差异。
因此,除了与Thumb代码交互之外,在带有MMU和实现32位ARM指令集的CPU上,是否有任何理由更喜欢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 s使速度较慢。
结果以每10000000次循环的秒数表示:

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作为返回指令。然而,带有ARM 1176的Raspberry Pi 1被记录为具有return prediction that recognises only 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}


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


然而,在我的Raspberry Pi 1上,从Raspbery Pi OS运行Linux 5.4.51+的ARM 1176没有显示可预测的指令的优势:

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

unguejic

unguejic1#

如果您正在测试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

字符串
如果我的假设是正确的,我预测mov pc, lr将比bx lr慢。
(可能需要更复杂的目标地址模式(在本例中为调用点)来混淆某些CPU上的间接分支预测。某些CPU具有只能记住1个目标地址的间接分支预测器,但稍微更复杂的预测器可以处理4个地址的简单重复模式。)
(This这是一个 * 猜测 *,我没有任何使用这些芯片的经验,但返回地址预测器的通用cpu架构技术是众所周知的,我读到过它在多个ISA上的实际应用。我确信x86使用它:http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/ Mismatched call/ret肯定是一个问题。)

相关问题