繁体   English   中英

手臂皮质-m4 循环计数有些奇怪

[英]Something weird with arm cortex-m4 cycle count

我最近使用了一块板( 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):);
    }
}
  • 根据Arm Infocenter ,指令MOV应该花费一个周期,但我发现

以下指令需要 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>

这两个 ldr 正在从 DWT->CYCCNT 读取,此外,为什么这会花费 10 个周期也很奇怪,我估计是 2(来自 ldr)+ 4 = 6

顺便说一下,板子没有任何缓存,我将代码存储在 sramx 中,堆栈在 sram2 中。

我是否错过了什么,有什么办法可以弄清楚每个周期是如何消耗的? 此外,我也对 cortex-m4 的数据依赖性感到困惑。

采取一个变化,我没有那个芯片,但有其他的。 在这种情况下使用 ti cortex-m4。 st 部分在闪存前面有这个缓存,我认为你不能关闭它并且(按照设计)影响性能。

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

因此,如果没有第二个 movw,则闪存中需要 0x11 个时钟,而内存中则需要 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 停在内核边缘的 arm 总线上(AXI、AMBA、AHB、APB 等等),一般来说,您可能有用于 L2 缓存的 ARM ip(不在这些微控制器中),并且您可以购买一些 arm ip 来帮助您处理他们的总线,但最终芯片中有芯片特定的东西,这与芯片供应商无关,并且芯片供应商之间不一致,特别是闪存和 sram 接口。

首先没有理由期望使用流水线处理器获得可预测的结果,如上所示,并且很容易用两条指令循环显示,相同的机器代码可能会因仅对齐而在性能上有很大差异,但也有一些因素你是直接或间接控制闪存等待状态,即时钟与闪存的相对速度。 如果我们设备上 N 和 N+1 等待状态之间的边界是 24Mhz,那么 N 等待状态下的 24Mhz 比 N+1 等待状态下的 24Mhz 快得多。 28Mhz(N+1 等待状态)在 N+1 等待状态下比 24Mhz 快,但最终 cpu 时钟可能会克服等待状态,你可以找到一个超过 24Mhz n+1 等待状态的 CPU 速度,就整体墙而言时钟定时性能,而不是被计数的 CPU 时钟,如果受闪存等待状态影响,被计数的 CPU 时钟应始终受闪存等待状态的影响。

sram 往往没有等待状态并且运行速度与 CPU 一样快,但可能有例外。 毫无疑问,外设是有限制的,很多厂商对外设时钟都有规定,这个不能超过32mhz,即使部分到48,那种东西,所以访问外设的基准会占用不同数量的cpu时钟在不同的 CPU/系统速度设置。

您还可以在处理器中配置选项,基本上是编译时选项。 cortex-m4 没有宣传这一点,但 cortex-m0+ 确实可以配置为 16 位或 32 位指令提取宽度。 我看不到该源代码,因此它可能必须是编译时,或者如果您选择可以设置控制寄存器并使其运行时可配置,或者可能具有说明 pll 设置是否正确的逻辑这样就强制一种方式,否则另一种方式,依此类推。 因此,即使您有两个来自不同供应商的具有相同 rev 和型号 cpu 内核的芯片,这并不意味着它们的行为相同。 更何况芯片厂商有源代码,可以修改。

因此,在您无法查看的系统中,尝试预测流水线处理器的周期数是不会发生的。 您有时会添加一个额外的 nop 并且它变得更快,有时您添加一个它会像人们预期的那样变慢,有时它不会改变。 如果 nop 可以做到这一点,那么任何其他指令也可以。

更不用说弄乱管道本身了,这些 cortex-ms 是非常短的管道,所以我们被告知如此强制具有大量依赖关系的指令序列与没有类似序列的指令序列不会产生太大的影响。

以相同的机器代码在不同厂商的多个 cortex-m4s(甚至 cortex-m3s 和 cortex-m7s)、flash 和 ram 上测试运行它,使用不同的设置,如果执行时间在cpu 滴答声有所不同。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM