我最近使用了一块电路板( LPCXpresso 5411x )进行了一些计算,我们试图减少周期,只要我们可以节省运行时间以满足我们的需求,所以我需要做一些关于cortex-m4指令如何成本循环的研究。而且我发现许多奇怪的东西(无法通过我从互联网上找到的东西来解释)
我使用 DWT-> CYCCNT 来计算我想要测试的函数所消耗的周期。
int start_cycle, end_cycle;
__asm volatile (
"LDR %[s1], [%[a]], #0\n\t"
:[s1] "=&r"(start_cycle): [a] "r"(&(DWT->CYCCNT)):);
AddrSumTest();
__asm volatile (
"LDR %[s1], [%[a]], #0\n\t"
:[s1] "=&r"(end_cycle): [a] "r"(&(DWT->CYCCNT)):);
printf("inside the func() cycles: %d\n",end_cycle - start_cycle);
以下是我的函数的定义方式:
__attribute__( ( always_inline )) static inline void AddrSumTest(){
uint32_t x, y, i, q;
__asm volatile (
"nop\n\t"
:[x] "=r" (x), [y] "=r" (y), [i] "=r" (i), [q] "=r" (q):);
}
}
以下说明需要8个周期(不是3个,因为从 DWT-> CYCCNT 读取需要额外的周期)
"nop\n\t"
"MOV %[x], #2\n\t"
"nop\n\t"
添加另一条MOV指令后,以下周期需要10个周期(为什么不是9个周期)
"nop\n\t"
"MOV %[x], #2\n\t"
"MOV %[y], #3\n\t"
"nop\n\t"
,后一种情况的汇编代码是
4000578: f853 4b00 ldr.w r4, [r3], #0
400057c: bf00 nop
400057e: f04f 0502 mov.w r5, #2
4000582: f04f 0603 mov.w r6, #3
4000586: bf00 nop
4000588: f853 1b00 ldr.w r1, [r3], #0
400058c: 4805 ldr r0, [pc, #20] ;(40005a4<test_AddrSum+0x30>)
400058e: 1b09 subs r1, r1, r4
4000590: f000 f80e bl 40005b0 <__printf_veneer>
两个ldrs正在读取DWT-&gt; CYCCNT,此外,为什么这将花费10个周期,而我估计的是2(来自ldr)+ 4 = 6
,这也很奇怪顺便说一下,主板没有任何缓存,我在sramx中存储代码,堆栈在sram2中。
我是否会错过任何东西,而且我有什么方法可以弄清楚每个周期是如何消耗的?此外,我也对cortex-m4的数据依赖性感到困惑。
答案 0 :(得分:1)
00000082 <test>:
82: f3bf 8f4f dsb sy
86: f3bf 8f6f isb sy
8a: 6802 ldr r2, [r0, #0]
8c: 46c0 nop ; (mov r8, r8)
8e: 46c0 nop ; (mov r8, r8)
90: 46c0 nop ; (mov r8, r8)
92: 46c0 nop ; (mov r8, r8)
94: 46c0 nop ; (mov r8, r8)
96: 46c0 nop ; (mov r8, r8)
98: f240 0102 movw r1, #2
9c: f240 0103 movw r1, #3
a0: 46c0 nop ; (mov r8, r8)
a2: 46c0 nop ; (mov r8, r8)
a4: 46c0 nop ; (mov r8, r8)
a6: 46c0 nop ; (mov r8, r8)
a8: 46c0 nop ; (mov r8, r8)
aa: 46c0 nop ; (mov r8, r8)
ac: 46c0 nop ; (mov r8, r8)
ae: 6803 ldr r3, [r0, #0]
b0: 1ad0 subs r0, r2, r3
b2: 4770 bx lr
因此,如果没有第二个运动,它在闪存中需要0x11个时钟,在RAM中需要0x10和0x11,具体取决于对齐方式。当thumb2指令在字边界上对齐时,它比未对齐时花费的时间更长。
使用拇指指令0x2102
00000000 20001016 00000010
00000002 20001018 00000010
00000004 2000101A 00000010
00000006 2000101C 00000010
使用thumb2扩展名0xf240,0x0102
00000000 20001016 00000010
00000002 20001018 00000011
00000004 2000101A 00000010
00000006 2000101C 00000011
使用thumb2扩展名0xf240,0x0102,0xf240,0x0103
00000000 20001016 00000012
00000002 20001018 00000013
00000004 2000101A 00000012
00000006 2000101C 00000013
这并不奇怪,可能与抓取有关。这些微控制器比全尺寸臂更简单。完整大小将获取每次获取8个指令,并且取决于获取行中的位置可能会影响性能,还有循环以及分支位于获取行中的位置(无论缓存是打开还是关闭)都无关紧要。分支机构还具有可以打开和关闭的分支预测器,并且可以在设计上有所不同。
这个特殊的芯片表示高于40Mhz它可以实现取出一个字的预取,这意味着它下面取一个半字(总线可能是一个字宽,因此读取相同的地址两次以获得两个指令...为什么?)
其他芯片(cortex-ms以及其他芯片)你必须控制闪存上的等待状态,有时闪存速度是ram的一半,相同的代码,相同的机器代码,在ram上运行得更快,即使在低速时只会增加时钟并增加闪光灯上的等待状态数以控制速度。
特别是ST系列有一些营销术语,用于预置缓存,它们放入你无法禁用。您可以在测试代码之前执行dsb / isb,例如,查看单次传递的等待状态的影响,但是如果执行测试循环
test_loop: sub r3,#1
bne test_loop
并且运行它很多次开头的那几个时钟被反射但很小,就像使用缓存一样,但是如果处理器让你看到它们,你仍应该看到对缓存的获取行效果。
某些芯片具有可以启用或禁用的闪存预取功能,尤其是循环可能会损害性能,而不是帮助您将事情调整得恰到好处,以便预取器在循环结束时读取得很好。
ARM ip停在核心边缘的臂总线上(AXI,AMBA,AHB,APB,无论如何),通常你可能有ARM ip用于L2缓存(不在其中一个微控制器中)你可能购买一些arm ip来帮助你使用他们的总线,但最终芯片中有芯片特定的东西,哪个臂与芯片供应商无关,从芯片供应商到芯片供应商,特别是flash和sram接口都不一致。 / p>
首先没有理由期望使用流水线处理器获得可预测的结果,如上所示,并且很容易通过双指令循环显示,相同的机器代码由于单独的对齐而在性能方面可以变化很大,但也有因素您可以直接或间接控制闪存等待状态,时钟与闪存的相对速度。如果我们设备上的N和N + 1等待状态之间的边界是24Mhz,那么N等待状态下的24Mhz比N + 1等待状态下的24Mhz快得多。在N + 1等待状态下,28Mhz(N + 1等待状态)比24Mhz快,但最终cpu时钟可以克服等待状态,你可以找到一个优于24Mhz n + 1等待状态的CPU速度,就整体墙而言时钟定时性能,不计算CPU时钟,如果受闪存等待状态的影响,CPU计数应始终受闪存等待状态的影响。
srams往往没有等待状态并且运行速度与CPU一样快,但可能有例外。毫无疑问,periperhals有限制,许多供应商都有关于外围时钟的规则,这一个不能超过32mhz,即使该部分达到48,那样的事情,所以访问外设的基准将需要不同数量的cpu时钟在不同的CPU /系统速度设置。
您还可以在处理器中配置可配置的选项,基本上是编译时间选项。 cortex-m4并没有宣传这一点,但是cortex-m0 +可以配置为16或32位指令获取宽度。我没有对该源代码的可见性所以它可能是必须是编译时间的东西,如果你选择你可以设置一个控制寄存器并让它运行时可配置,或者可能有逻辑说明如果pll设置是这样的话强行一方,另一方,等等。因此,即使你有两个来自不同供应商的芯片具有相同的转速和型号cpu核心,这并不意味着它们的行为会相同。更不用说芯片供应商有源代码并且可以进行修改。
因此,尝试预测您无法查看的系统中的流水线处理器的循环计数是不会发生的。您将有时间添加一个额外的nop并且它会变得更快,您添加一个的时间会变得更快,并且它会像预期的那样变慢并且不会变化。如果一个nop可以做到这一点,那么任何其他指令也可以。
更不用说管道本身了,这些cortex-ms实际上是短管道,所以我们被告知如此强制一系列指令与很多依赖关系相比,类似的序列没有那么大的影响。
在测试中使用相同的机器代码在来自不同供应商的几个cortex-m4(甚至是cortex-m3s和cortex-m7s),flash和ram,使用不同的设置运行它,如果cpu ticks中的执行时间各不相同。