除了 ARMv7 之前的拇指互通之外,还有什么理由在 MOV pc、R 上使用 BX R 吗?

问题描述 投票:0回答:1

Linux 定义了一个汇编器宏,以便在支持它的 CPU 上使用

BX
,这让我怀疑存在一些性能原因。

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

但是,我的基准测试工作未能发现 ARM1176、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
会使速度变慢。

每 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, lr
bx lr
识别为返回指令。然而,带有 ARM1176Raspberry 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
assembly arm cpu-architecture branch-prediction micro-architecture
1个回答
4
投票

如果您正在测试

mov pc, ...
始终跳转到相同返回地址的简单情况,则常规间接分支预测可能会很好。

我猜测

bx lr
可能会使用一个返回地址预测器,该预测器假设匹配调用/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 个地址的简单重复模式。)


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

© www.soinside.com 2019 - 2024. All rights reserved.