8

Linux 定义了一个汇编器宏BX在支持它的 CPU 上使用,这让我怀疑存在一些性能原因。

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

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

因此,除了与 Thumb 代码交互之外,是否有任何理由更喜欢使用 MMU 并实现 32 位 ARM 指令集的 CPU BXMOV 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}

删除nops 会更慢。

结果为每 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 %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

然而,在我的带有 ARM1176 的 Raspberry Pi1 上,运行来自 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
4

1 回答 1

2

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

我猜想bx lr可能会使用一个返回地址预测器,该预测器假设匹配调用/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 架构技术是众所周知的,我已经读过它在多个 ISA 上实际使用。我确定 x86 使用它: http: //blog.stuffedcow.net/2018/04/ras-microbenchmarks/ Mismatched call/ret 绝对是一个问题。)

于 2020-08-09T00:20:29.540 回答