ARM架构下BX指令相较MOV pc,R的非Thumb互用优势探究
关于ARM架构中BX vs MOV pc,R的性能与设计考量
你提到Linux里优先用BX的汇编宏,确实有历史和架构设计层面的原因——虽然你的基准测试在多款现代ARM核上没测出明显差异,但还是得从分支预测硬件设计、架构兼容性和官方语义规范这几个角度拆解:
1. 早期ARM核的分支预测硬件限制
ARM1176的文档指出返回预测仅识别BX lr及部分加载指令,这是早期ARM核分支预测单元(BPU)的设计约束:这类核的返回栈预测器(RSP)只会把BX lr视为函数返回的标志性指令,而MOV pc, lr不会触发RSP的预测动作。
至于你的树莓派1测试没体现出差异,可能的原因包括:
- 测试场景里的无效计算指令耗时远超过分支延迟,掩盖了分支预测的优势
- 内核版本或编译器优化导致实际执行的指令和手写汇编存在差异
- ARM1176的RSP本身预测精度有限,在特定负载下优势不明显
2. 全系列架构兼容性的前瞻性设计
Linux的宏优先用BX,本质是为了兼容全系列ARM核——哪怕现代核(Cortex-A72、Neoverse-N1)已经能识别MOV pc, lr作为返回指令,但早期核(比如ARM9、部分ARM11型号)做不到。用BX可以保证在所有支持ARMv4T及以上的核上都能获得最优的分支预测表现,不用针对不同核做分支处理。
另外,BX本身是ARM架构里标准的返回指令语义,ARM官方编程指南也推荐用BX lr(或BLX)作为函数返回,而MOV pc, lr更多是早期没有BX指令时的替代方案,属于“兼容式写法”而非“最优写法”。
3. 你的基准测试数据解读
从你给出的测试结果来看:
- 在Cortex-A17、A72、Neoverse-N1这些现代核上,
BX lr和MOV pc, lr的性能几乎无差异,说明这些核的BPU已经能把两种指令都识别为返回操作,触发RSP预测 div_bx2(在r3上做无效计算)比div_bx(操作lr)快很多,这是因为lr是链接寄存器,部分核对lr的读写有额外的流水线约束,和BX/MOV本身无关- 移除BLX测试里的nop导致性能下降,是因为指令对齐和流水线填充的问题,和BX的分支预测无关
总结:优先用BX的理由(除Thumb互用外)
- 历史核的分支预测支持:早期ARM核的返回栈预测只识别
BX lr,用它能保证在老核上获得更好的性能 - 官方语义的规范性:ARM架构规范把
BX定义为标准的分支/返回指令,MOV pc, R属于通用寄存器跳转,语义上更偏向“任意跳转”而非“函数返回” - 未来兼容性:哪怕现有核已经支持两种指令的预测,未来的ARM核可能会对标准语义的
BX做更多优化,而MOV pc, R可能不会被优先考虑
补充测试代码与结果
核心测试代码(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}
每1亿次循环耗时(秒)
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 |
扩展测试代码与结果
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(树莓派1)测试结果
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
内容的提问来源于stack exchange,提问作者Timothy Baldwin




