被测代码:
ldr r2,[r0]
---- code under test ----
loop:
subs r1,#1
bne loop
---- code under test ----
ldr r3,[r0]
subs r0,r2,r3
bx lr
R0 包含 systick 计数寄存器中的点,被测代码基本上是尽可能紧密和可重复地测量的。 Systick 和 DWT 一样好,而且可能更容易。
我从 ram 运行,因为这是一个 mcu,但更重要的是因为它是一个 STM32,除了极少数例外,它在闪存前面有一个预取缓存,你不能禁用也不能轻易破坏,从而对 PITA 进行基准测试。
在低位寄存器和高位寄存器之间切换
add r3,r10,r11
add r3,r3,r4
c: eb0a 030b add.w r3, sl, fp
c: 4423 add r3, r4
基本上是两个半字对一个。通过更改某些指令中的某些寄存器会导致指令大小发生变化,这种指令大小变化会影响后面代码的对齐方式。
测试代码
10: 6802 ldr r2, [r0, #0]
00000012 <loop>:
12: 3901 subs r1, #1
14: d1fd bne.n 12 <loop>
16: 6803 ldr r3, [r0, #0]
18: 1ad0 subs r0, r2, r3
1a: 4770 bx lr
循环未在字边界 (0x12) 上对齐
测试使用 0x10000、0x10000、0x20000、0x30000 循环运行四次(r1=0x10000,...)
给予
0005FFFD
0005FFFD
000BFFFD
0011FFFD
所以现在相同的机器代码,在字边界上对齐。
00000010 <loop>:
10: 3901 subs r1, #1
12: d1fd bne.n 10 <loop>
00040001
00040001
00080001
000C0001
机器码在每种情况下都匹配
12: 3901 subs r1, #1
14: d1fd bne.n 12 <loop>
10: 3901 subs r1, #1
12: d1fd bne.n 10 <loop>
通过半字改变对齐方式,性能发生巨大变化
当然,无论何时进行任何 ARM 汇编语言编程,您都应该随时准备好 ARM 文档:
“所有提取都是字宽的。每个字提取的指令数取决于正在运行的代码和内存中代码的对齐方式。”
请注意,不同的 Cortex-M 内核可能有不同的获取选项,不要假设因为 Cortex-M4 说的是一回事,其他的并没有做不同的事情。
为了记录,上面的简单示例并未说明闪存中的问题。 STM32 产品有一个闪存缓存(有一些花哨的营销名称和商标,也许还有专利),您无法关闭(通常)。
这在 Flash 中有效
08000032 <loop>:
8000032: f3af 8000 nop.w
8000036: f3af 8000 nop.w
800003a: f3af 8000 nop.w
800003e: f3af 8000 nop.w
8000042: f3af 8000 nop.w
8000046: 3901 subs r1, #1
8000048: d1f3 bne.n 8000032 <loop>
00090000
00090000
00120000
001B0000
对
08000030 <loop>:
8000030: f3af 8000 nop.w
8000034: f3af 8000 nop.w
8000038: f3af 8000 nop.w
800003c: f3af 8000 nop.w
8000040: f3af 8000 nop.w
8000044: 3901 subs r1, #1
8000046: d1f3 bne.n 8000030 <loop>
00080000
00080000
00100000
00180000
相同的机器码,半字对齐改变。快 12.5%。
一些非 STM32 的这种工作要容易得多(在闪存中),因为它们没有花哨的缓存和/或依赖于您可以启用/禁用的 ARM 缓存。