【问题标题】:Why does changing to a low register in thumb2 increase the cycle count?为什么在 thumb2 中更改为低寄存器会增加循环计数?
【发布时间】:2020-09-12 17:15:58
【问题描述】:

我已将 thumb2 汇编代码中的所有 r10 更改为 r4。 正如预期的那样,尺寸确实缩小了一点。
但是执行的周期增加了。虽然仍然有相同的说明,但现在只有一些是窄的而不是宽的。

为什么会这样?

根本没有使用中断,只访问闪存和内存(在定时例程内)。

我在 Cortex-M4 (STM32F411) 上运行我的代码,等待状态为 0,并且有或没有预取和/或 icache。我用 DWT:CYCCNT 测量过它。 它从 1902619 个周期增加到 1908268 个周期,大约 0.2%。 (一些使用/-out icache/prefetch 的周期很谨慎。)

【问题讨论】:

  • 代码是否足够短,您可以共享两个版本,或者您可以通过一个简短的示例重现该现象吗?它可能不是通用效果,而是特定于您正在做的事情。
  • 后面指令的代码对齐可能很重要。如果没有minimal reproducible example 或任何关于周围代码的线索,这显然是无法回答的。
  • 当 32 位 thumb 指令不是字对齐时,它是否可能需要更多周期?我已将所有 32 位指令与字边界对齐,现在速度更快了。
  • 似乎字对齐对于宽分支指令很重要,否则它们需要更多的周期。在我测试过的 STM32F411 上。
  • "所有提取都是字宽的。每个字提取的指令数取决于正在运行的代码和内存中代码的对齐方式。"阅读文档

标签: performance assembly arm cortex-m thumb


【解决方案1】:

被测代码:

    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 缓存。

【讨论】:

    猜你喜欢
    • 2016-11-25
    • 1970-01-01
    • 2012-06-24
    • 1970-01-01
    • 2017-08-30
    • 2014-01-31
    • 2020-03-10
    • 1970-01-01
    • 2019-11-03
    相关资源
    最近更新 更多