[很多文字传入,因为我想尽我所能详细说明我的问题。]
我正在优化Cortex-M0的手写ARM汇编代码。我正在使用的主板是STMicro STM32F0Discovery,它有一个STM32F051R8控制器。控制器以48 MHz运行。
不幸的是,在进行优化时,我得到了一些非常奇怪的循环计数。
例如,在我的代码中将一个nop
添加到循环中应该总共添加2个循环(循环2次)。但是,这样做会增加大约1800个额外周期。现在,当我添加额外的nop
(总共2 nop
s)时,循环计数确实增加了预期的4个周期。
对于下面的示例代码,我得到了类似的奇怪结果。示例代码显示了顶部摘录:c = 25 * a + 5 * b
。底部摘录为c = 5 * (5 * a + b)
。因此,底部应该更快,因为它需要少1 mov
。但是,改变这个:
movs r4, #25
muls r3, r4, r3
add r2, r3
ldrb r3, [r6, #RoundStep]
movs r4, #5
muls r3, r4, r3
add r2, r3
进入这个:
movs r4, #5
muls r3, r4, r3
ldrb r5, [r6, #RoundStep]
add r3, r5
muls r3, r4, r3
add r2, r3
不会将速度提高预期的1个周期,相反,它会使速度降低或多或少1000个周期......
要计算周期,我使用SysTick计数器,从其最大值开始倒计数,并在溢出中断时增加溢出计数器。我正在使用的代码与ARM网站上的this excerpt大致相同,但为我正在使用的Cortex-M0重写了代码。我的代码足够快,在测量过程中不会发生溢出中断。
现在,我开始认为计数器给了我错误的值,所以我还为我曾经躺着的TI Stellaris LaunchPad写了一些代码。这是一个运行频率为80 MHz的Cortex-M4F。该代码测量某个引脚保持高电平的周期数。当然,M0的时钟和M4F的时钟没有同步运行,因此报告的周期计数略有不同,我通过采用测量周期计数的非常低加权指数平均值来“修复”{{{ 1}})并重复测量10000次。
M4F测量的时间与M0测量的时间相同,所以“不幸的是”看起来SysTick计数器在M0中运行得很好。
起初我认为这些额外的延迟是由管道停滞造成的,但一方面M0似乎太简单了,而另一方面我找不到M0管道的任何详细信息,所以我可以验证。
所以,我的问题是:这里发生了什么?为什么添加单个avg = 0.995 * avg + 0.005 * curCycles
使我的函数需要额外的1000个循环/循环,但是两个nop
只会使循环计数增加2?如何删除指令会使我的代码执行得更慢?
答案 0 :(得分:3)
mul
指令可以是ALU管道的multiple cycles。您将c = 25 * a + 5 * b
转换为c = 5 * (5 * a + b)
只需要少mov
。但是,管道的加载存储阶段与ALU重叠。这些通常是单独的阶段,通过ldrb
指令,您可以获得免费的mov
说明。此外,根据值,muls
可能执行得更快;具体而言,顶部字节为零通常会导致分拣机乘法循环。第一个版本中的数据依赖性少得多;指令 n 没有与 n + 1 共用的寄存器。这是允许管道衬里的基本要求。
比较,
ldrb r5, [r6, #RoundStep] ; 2 cycles
add r3, r5 ; must block for r5 to load (1 cycle)
用,
ldrb r3, [r6, #RoundStep] ; 2 cycles
movs r4, #5 ; may run in parallel with above.
因此,即使您可以将指令数量相加并且代码更少,但由于 pipe-lining 或instruction scheduling,可能会发现较大的备用代码运行得更快。
如果您可以将ldrb
重新定位到例程的开头,那么2 nd 版本可能会更快。