使用的指令数 ARMv7

Number of instructions used ARMv7

我正在尝试计算将使用多少 CPU 个周期来执行延迟功能

delay:
 subs r0, #1
 bmi end_delay
 b delay
 end_delay:
 bx lr

我直觉认为每条指令应该使用 1 CPU 个周期,所以如果我们从 r0 =4 开始,将需要 11 CPU 个周期来完成下面的代码,对吗?

I feel intuitively that 1 CPU cycle should be used for each instruction, so if we began with r0 =4 it would take 11 CPU cycles to complete the following code is that correct ?

鉴于大多数 ARM CPU 有 3-8 个流水线阶段,很难说大部分指令需要 1 CPU 周期才能完成。理想情况下,在流水线 CPU 中,每个时钟周期都应该有一条指令退出,但由于上面的代码有分支语句,因此很难判断每条指令何时退出。原因是我们不知道如何处理分支,因为这取决于处理器设计中存在的分支预测器算法。因此,如果预测正确,则不会在管道中插入任何气泡,但如果预测错误,则取决于内部管道结构将插入多少气泡。对于理想的 5 级流水线,每次错误预测都会插入 2 个气泡。但这又取决于内部微架构的实现。 因此,很难准确预测上述代码需要多少个周期。

cortex-m 与微芯片 pic 芯片(或 z80 和其他一些)不同,您无法使用此指令集以这种方式创建可预测的延迟。您可以确保它会在或更慢但在某些时间(时钟)不正确。

0000009c <hello>:
  9c:   3801        subs    r0, #1
  9e:   d1fd        bne.n   9c <hello>

你的循环有一个 b运行ch 决定,更多的指令和更多的路径基本上所以执行时间变化的机会变得更糟。

00000090 <delay>:
  90:   3801        subs    r0, #1
  92:   d400        bmi.n   96 <end_delay>
  94:   e7fc        b.n 90 <delay>

00000096 <end_delay>:

所以如果我们关注这三个指令。

一些 cortex-ms 有一个构建(逻辑)时间选项,可以按指令或按字获取,cortex-m4 文档说:

All fetches are word-wide.

所以我们希望半字对齐不会影响性能。有了这些说明,我们不一定期望看到差异。对于全尺寸的手臂,提取是多个单词,因此您肯定会看到提取行(大小)的影响。

执行在很大程度上取决于实施。 cortex-m 只是 arm 内核,芯片的其余部分来自芯片供应商,购买 IP 或内部构建或组合(很可能是后者)。 ARM 不制造芯片(除了可能用于验证),他们制造出售的 IP。

芯片供应商决定闪存(和 ram)的实现,对于这些类型的芯片,闪存速度通常等于或低于 cpu 速度,这意味着它可能需要两个时钟来获取一条指令意味着你永远不会尽可能快地喂 cpu。有些像 ST 有一个缓存,你不能(据我所知)关闭,所以很难看到这种效果(但仍然可能),我为此使用的特定芯片说:

8.2.3.1 Prefetch Buffer The Flash memory controller has a prefetch buffer that is automatically used when the CPU frequency is greater than 40 MHz. In this mode, the Flash memory operates at half of the system clock. The prefetch buffer fetches two 32-bit words per clock allowing instructions to be fetched with no wait states while code is executing linearly. The fetch buffer includes a branch speculation mechanism that recognizes a branch and avoids extra wait states by not reading the next word pair. Also, short loop branches often stay in the buffer. As a result, some branches can be executed with no wait states. Other branches incur a single wait state.

当然,就像 ST 一样,他们并没有真正告诉您整个故事。所以我们就进去试试这个。如果你愿意,你可以使用调试计时器,但是 systick 运行 关闭同一个时钟并给你相同的结果

00000086 <test>:
  86:   f3bf 8f4f   dsb sy
  8a:   f3bf 8f6f   isb sy
  8e:   680a        ldr r2, [r1, #0]

00000090 <delay>:
  90:   3801        subs    r0, #1
  92:   d400        bmi.n   96 <end_delay>
  94:   e7fc        b.n 90 <delay>

00000096 <end_delay>:
  96:   680b        ldr r3, [r1, #0]
  98:   1ad0        subs    r0, r2, r3
  9a:   4770        bx  lr

所以我读了 CCR 和 CPUID

00000200 CCR
410FC241 CPUID

只是因为。然后运行被测代码三遍

00000015
00000015
00000015

这些数字是十六进制的,所以是 21 条指令。每次执行时间相同,因此没有缓存或 b运行ch 预测缓存效果。我没有在 cortex-m4 上看到任何与 b运行ch 预测相关的信息,其他 cortex-ms 确实有 b运行ch 预测(可能只有 m7)。我关闭了 I 和 D 缓存,它们当然会与对齐一起极大地影响执行时间(并且该时间 can/will 因您的应用程序 运行s 而异)。

我更改了对齐方式(在此代码前添加或删除 nop)

0000008a <delay>:
  8a:   3801        subs    r0, #1
  8c:   d400        bmi.n   90 <end_delay>
  8e:   e7fc        b.n 8a <delay>

并没有影响执行时间。

AFAIK 使用此处理器我们无法直接更改闪存等待状态设置,它是根据时钟设置自动设置的,因此 运行宁以不同的时钟速度,在 40Mhz 标记以上我得到

0000001E                                                                                         
0000001E                                                                                         
0000001E 

对于相同的机器代码,相同的对齐现在是 30 个时钟而不是 21 个。

通常 ram 更快并且没有等待状态(了解这些总线每个 t运行action 需要几个时钟,所以它不像过去那样,但仍然有延迟你可以检测到),所以 运行在 ram 中使用这些指令应该告诉我们一些事情

for(rb=0;rb<0x20;rb+=2)
{

    hexstrings(rb);
    ra=0x20001000+rb;
    PUT16(ra,0x680a); ra+=2;
    hexstrings(ra);
    PUT16(ra,0x3801); ra+=2;
    PUT16(ra,0xd400); ra+=2;
    PUT16(ra,0xe7fc); ra+=2;
    PUT16(ra,0x680b); ra+=2;
    PUT16(ra,0x1ad0); ra+=2;
    PUT16(ra,0x4770); ra+=2;

    PUT16(ra,0x46c0); ra+=2;
    PUT16(ra,0x46c0); ra+=2;
    PUT16(ra,0x46c0); ra+=2;
    PUT16(ra,0x46c0); ra+=2;
    PUT16(ra,0x46c0); ra+=2;
    PUT16(ra,0x46c0); ra+=2;
    hexstring(BRANCHTO(4,STCURRENT,0x20001001+rb)&STMASK);
}

这肯定会变得有趣...

00000000 20001002 00000026                                                                       
00000002 20001004 00000020                                                                       
00000004 20001006 00000026                                                                       
00000006 20001008 00000020                                                                       
00000008 2000100A 00000026                                                                       
0000000A 2000100C 00000020                                                                       
0000000C 2000100E 00000026                                                                       
0000000E 20001010 00000020                                                                       
00000010 20001012 00000026                                                                       
00000012 20001014 00000020                                                                       
00000014 20001016 00000026                                                                       
00000016 20001018 00000020                                                                       
00000018 2000101A 00000026                                                                       
0000001A 2000101C 00000020                                                                       
0000001C 2000101E 00000026                                                                       
0000001E 20001020 00000020 

首先是32或38个时钟,其次是对齐效果

armv7-m CCR 显示了一个 b运行ch 预测位,但 trm 和供应商文档没有显示它,所以它可能是一个通用的东西,并非所有内核都支持。

因此,对于特定的 cortex-m4 芯片,执行循环的时间在 21 到 38 个时钟之间,如果我愿意,我可能会减慢它的速度。不过我不认为我可以在这个芯片上把它降到 11。

例如,如果您正在执行 i2c bit banging,您可以使用类似这样的延迟,它可以很好地工作,不会是最佳的,但会很好地工作。如果您在 window 的时间内需要更精确的东西,至少这个但不大于然后使用计时器(并了解轮询或中断您的准确性会有一些错误)如果计时器外围设备或其他可以生成信号你想要你可以得到一个时钟精确的波形(如果那是你的延迟的目的)。

另一个 cortex-m4 预计会有不同的结果,我希望 stm32 的 sram 与闪存相同或更快,而不是像这种情况下那样慢。如果您依靠其他人来设置您的芯片,有些设置可能会弄乱您的初始化代码,can/will 会影响执行时间。

编辑

我不知道我从哪里得到这个想法是用于 cortex-m4,它是一个 armv7-m,所以我没有 raspberry pi 2 方便,但有一个 pi3,和 运行 在 aarch32 模式下,32 位指令。我不知道要让计时器 运行ning 然后启用缓存需要多少工作。 dram 中的 pi 运行s 即使是裸机也非常不一致。所以我想我会启用 l1 缓存,并且在第一个 运行 之后它应该全部在缓存中并且是一致的。现在我想到它有四个核心,每个都是 运行ning,不知道如何禁用它们,其他三个在循环中旋转,等待邮箱寄存器告诉它们要 运行 的代码.也许我需要将它们 b运行ch 放在某个地方,并且 运行 也从 l1 缓存中取出...不确定 l1 是按核心还是共享,我想我曾经查过。

无论如何代码都在测试中

000080c8 <COUNTER>:
    80c8:   ee192f1d    mrc 15, 0, r2, cr9, cr13, {0}

000080cc <delay>:
    80cc:   e2500001    subs    r0, r0, #1
    80d0:   4a000000    bmi 80d8 <end_delay>
    80d4:   eafffffc    b   80cc <delay>

000080d8 <end_delay>:
    80d8:   ee193f1d    mrc 15, 0, r3, cr9, cr13, {0}
    80dc:   e0430002    sub r0, r3, r2
    80e0:   e12fff1e    bx  lr

妙语是针对该对齐,第一列是通过的 r0,接下来的三列是三个 运行s,最后一列如果与之前的 运行 当前有差异(r0中额外计数值的成本)

00000000 0000000A 0000000A 0000000A 
00000001 00000014 00000014 00000014 0000000A 
00000002 0000001E 0000001E 0000001E 0000000A 
00000003 00000028 00000028 00000028 0000000A 
00000004 00000032 00000032 00000032 0000000A 
00000005 0000003C 0000003C 0000003C 0000000A 
00000006 00000046 00000046 00000046 0000000A 
00000007 00000050 00000050 00000050 0000000A 
00000008 0000005A 0000005A 0000005A 0000000A 
00000009 00000064 00000064 00000064 0000000A 
0000000A 0000006E 0000006E 0000006E 0000000A 
0000000B 00000078 00000078 00000078 0000000A 
0000000C 00000082 00000082 00000082 0000000A 
0000000D 0000008C 0000008C 0000008C 0000000A 
0000000E 00000096 00000096 00000096 0000000A 
0000000F 000000A0 000000A0 000000A0 0000000A 
00000010 000000AA 000000AA 000000AA 0000000A 
00000011 000000B4 000000B4 000000B4 0000000A 
00000012 000000BE 000000BE 000000BE 0000000A 
00000013 000000C8 000000C8 000000C8 0000000A 

然后使对齐检查更容易,我最终不需要这样做 让它对上面的代码(第一列中的地址)尝试不同的对齐方式,结果是 r0 为 4。

00010000 00000032 00010004 0000002D 00010008 00000032 0001000C 0000002D

这重复到地址 0x101FC

如果我在编译测试中更改对齐方式

000080cc <COUNTER>:
    80cc:   ee192f1d    mrc 15, 0, r2, cr9, cr13, {0}

000080d0 <delay>:
    80d0:   e2500001    subs    r0, r0, #1
    80d4:   4a000000    bmi 80dc <end_delay>
    80d8:   eafffffc    b   80d0 <delay>

000080dc <end_delay>:
    80dc:   ee193f1d    mrc 15, 0, r3, cr9, cr13, {0}
    80e0:   e0430002    sub r0, r3, r2
    80e4:   e12fff1e    bx  lr

那就快一点点。

00000000 00000009 00000009 00000009 
00000001 00000012 00000012 00000012 00000009 
00000002 0000001B 0000001B 0000001B 00000009 
00000003 00000024 00000024 00000024 00000009 
00000004 0000002D 0000002D 0000002D 00000009 
00000005 00000036 00000036 00000036 00000009 
00000006 0000003F 0000003F 0000003F 00000009 
00000007 00000048 00000048 00000048 00000009 
00000008 00000051 00000051 00000051 00000009 
00000009 0000005A 0000005A 0000005A 00000009 
0000000A 00000063 00000063 00000063 00000009 
0000000B 0000006C 0000006C 0000006C 00000009 
0000000C 00000075 00000075 00000075 00000009 
0000000D 0000007E 0000007E 0000007E 00000009 
0000000E 00000087 00000087 00000087 00000009 
0000000F 00000090 00000090 00000090 00000009 
00000010 00000099 00000099 00000099 00000009 
00000011 000000A2 000000A2 000000A2 00000009 
00000012 000000AB 000000AB 000000AB 00000009 
00000013 000000B4 000000B4 000000B4 00000009 

如果我将其更改为函数调用

000080cc <COUNTER>:
    80cc:   e92d4001    push    {r0, lr}
    80d0:   ee192f1d    mrc 15, 0, r2, cr9, cr13, {0}
    80d4:   eb000003    bl  80e8 <delay>
    80d8:   ee193f1d    mrc 15, 0, r3, cr9, cr13, {0}
    80dc:   e8bd4001    pop {r0, lr}
    80e0:   e0430002    sub r0, r3, r2
    80e4:   e12fff1e    bx  lr

000080e8 <delay>:
    80e8:   e2500001    subs    r0, r0, #1
    80ec:   4a000000    bmi 80f4 <end_delay>
    80f0:   eafffffc    b   80e8 <delay>

000080f4 <end_delay>:
    80f4:   e12fff1e    bx  lr

00000000 0000001A 0000001A 0000001A 
00000001 00000023 00000023 00000023 00000009 
00000002 0000002C 0000002C 0000002C 00000009 
00000003 00000035 00000035 00000035 00000009 
00000004 0000003E 0000003E 0000003E 00000009 
00000005 00000047 00000047 00000047 00000009 
00000006 00000050 00000050 00000050 00000009 
00000007 00000059 00000059 00000059 00000009 
00000008 00000062 00000062 00000062 00000009 
00000009 0000006B 0000006B 0000006B 00000009 
0000000A 00000074 00000074 00000074 00000009 
0000000B 0000007D 0000007D 0000007D 00000009 
0000000C 00000086 00000086 00000086 00000009 
0000000D 0000008F 0000008F 0000008F 00000009 
0000000E 00000098 00000098 00000098 00000009 
0000000F 000000A1 000000A1 000000A1 00000009 
00000010 000000AA 000000AA 000000AA 00000009 
00000011 000000B3 000000B3 000000B3 00000009 
00000012 000000BC 000000BC 000000BC 00000009 
00000013 000000C5 000000C5 000000C5 00000009 

每次计数的成本相同,但调用开销更高

这允许我使用 thumb 模式只是为了好玩,以避免模式更改链接器添加我让它更快一点(并且一致)。

000080cc <COUNTER>:
    80cc:   e92d4001    push    {r0, lr}
    80d0:   e59f103c    ldr r1, [pc, #60]   ; 8114 <edel+0x2>
    80d4:   e59fe03c    ldr lr, [pc, #60]   ; 8118 <edel+0x6>
    80d8:   ee192f1d    mrc 15, 0, r2, cr9, cr13, {0}
    80dc:   e12fff11    bx  r1

000080e0 <here>:
    80e0:   ee193f1d    mrc 15, 0, r3, cr9, cr13, {0}
    80e4:   e8bd4001    pop {r0, lr}
    80e8:   e0430002    sub r0, r3, r2
    80ec:   e12fff1e    bx  lr

000080f0 <delay>:
    80f0:   e2500001    subs    r0, r0, #1
    80f4:   4a000000    bmi 80fc <end_delay>
    80f8:   eafffffc    b   80f0 <delay>

000080fc <end_delay>:
    80fc:   e12fff1e    bx  lr
    8100:   e1a00000    nop         ; (mov r0, r0)
    8104:   e1a00000    nop         ; (mov r0, r0)
    8108:   e1a00000    nop         ; (mov r0, r0)

0000810c <del>:
    810c:   3801        subs    r0, #1
    810e:   d400        bmi.n   8112 <edel>
    8110:   e7fc        b.n 810c <del>

00008112 <edel>:
    8112:   4770        bx  lr

00000000 000000F4 0000001B 0000001B 
00000001 00000024 00000024 00000024 00000009 
00000002 0000002D 0000002D 0000002D 00000009 
00000003 00000036 00000036 00000036 00000009 
00000004 0000003F 0000003F 0000003F 00000009 
00000005 00000048 00000048 00000048 00000009 
00000006 00000051 00000051 00000051 00000009 
00000007 0000005A 0000005A 0000005A 00000009 
00000008 00000063 00000063 00000063 00000009 
00000009 0000006C 0000006C 0000006C 00000009 
0000000A 00000075 00000075 00000075 00000009 
0000000B 0000007E 0000007E 0000007E 00000009 
0000000C 00000087 00000087 00000087 00000009 
0000000D 00000090 00000090 00000090 00000009 
0000000E 00000099 00000099 00000099 00000009 
0000000F 000000A2 000000A2 000000A2 00000009 
00000010 000000AB 000000AB 000000AB 00000009 
00000011 000000B4 000000B4 000000B4 00000009 
00000012 000000BD 000000BD 000000BD 00000009 
00000013 000000C6 000000C6 000000C6 00000009

与此对齐

0000810e <del>:
    810e:   3801        subs    r0, #1
    8110:   d400        bmi.n   8114 <edel>
    8112:   e7fc        b.n 810e <del>

00008114 <edel>:
    8114:   4770        bx  lr


00000000 0000007E 0000001C 0000001C 
00000001 00000026 00000026 00000026 0000000A 
00000002 00000030 00000030 00000030 0000000A 
00000003 0000003A 0000003A 0000003A 0000000A 
00000004 00000044 00000044 00000044 0000000A 
00000005 0000004E 0000004E 0000004E 0000000A 
00000006 00000058 00000058 00000058 0000000A 
00000007 00000062 00000062 00000062 0000000A 
00000008 0000006C 0000006C 0000006C 0000000A 
00000009 00000076 00000076 00000076 0000000A 
0000000A 00000080 00000080 00000080 0000000A 
0000000B 0000008A 0000008A 0000008A 0000000A 
0000000C 00000094 00000094 00000094 0000000A 
0000000D 0000009E 0000009E 0000009E 0000000A 
0000000E 000000A8 000000A8 000000A8 0000000A 
0000000F 000000B2 000000B2 000000B2 0000000A 
00000010 000000BC 000000BC 000000BC 0000000A 
00000011 000000C6 000000C6 000000C6 0000000A 
00000012 000000D0 000000D0 000000D0 0000000A 
00000013 000000DA 000000DA 000000DA 0000000A 

所以在这个处理器上的某个理想世界中假设缓存命中延迟代码

00000004 00000032 00000032 00000032 0000000A 
00000004 0000002D 0000002D 0000002D 00000009 
00000004 0000003E 0000003E 0000003E 00000009 
00000004 0000003F 0000003F 0000003F 00000009 
00000004 00000044 00000044 00000044 0000000A 

0x2D 和 0x44 之间的时钟到 运行 r0 = 4 循环

实际上在这个平台上没有启用缓存 and/or 如果缓存未命中,您可能会看到什么。

00000000 0000030B 000002B7 000002ED 
00000001 0000035B 00000389 000003E9 
00000002 000003FB 00000439 0000041B 
00000003 0000058F 000004E7 0000055B 
00000004 000005FF 0000069D 000006D1 
00000005 00000745 00000733 000006F7 
00000006 00000883 00000817 00000801 
00000007 00000873 00000853 0000089B 
00000008 00000923 00000B05 0000092F 
00000009 00000A3F 000009A9 00000B4D 
0000000A 00000B79 00000BA9 00000C57 
0000000B 00000C21 00000D13 00000B51 
0000000C 00000C0B 00000E91 00000DE9 
0000000D 00000D97 00000E0D 00000E81 
0000000E 00000E5B 0000100B 00000F25 
0000000F 00001097 00001095 00000F37 
00000010 000010DB 000010FD 0000118B 
00000011 00001071 0000114D 0000123F 
00000012 000012CF 0000126D 000011DB 
00000013 0000140D 0000143D 0000141B 
000002B7 0000143D 

r0=4 行

00000004 000005FF 0000069D 000006D1 

那是很多 cpu 计数...

希望我已经解决了这个话题。虽然尝试假设代码 运行s 有多快或计数多少等很有趣......但在这些类型的处理器、管道、缓存、b运行ch 预测上并不是那么简单,复杂的系统总线,在各种芯片实现中使用通用内核,其中芯片供应商管理 memory/flash 与处理器 IP 供应商代码分开。

我没有在第二个实验中弄乱 b运行ch 预测,如果我这样做了,那么对齐就不会那么一致,这取决于 b运行ch 预测的实现方式,它可能会有所不同它的有用性基于 b运行ch 相对于提取行的位置,因为下一次提取已经开始或没有开始,或者当 b运行ch 预测器确定它不需要这样做时是某种方式fetch and/or 启动 b运行ched 获取,在这种情况下 b运行ch 领先两个所以你可能看不到这段代码,你会想要一些 nop 洒在中间所以bmi 目标位于单独的提取行中(以便查看差异)。

这是很容易操作的东西,使用相同的机器代码序列并看到它们的执行时间因我们所看到的而异。在 0x3F 和 0x6D1 之间,最快和最慢之间的差异超过 27 倍......对于相同的机器代码。通过一条指令更改代码的对齐方式(不相关代码中的其他地方多了一条或少了一条来自先前构建的指令)是 5 个计数差异。

公平地说,测试结束时的 mrc 可能是时间的一部分

000080c8 <COUNTER>:
    80c8:   ee192f1d    mrc 15, 0, r2, cr9, cr13, {0}
    80cc:   ee193f1d    mrc 15, 0, r3, cr9, cr13, {0}
    80d0:   e0430002    sub r0, r3, r2
    80d4:   e12fff1e    bx  lr

无论哪种对齐方式,结果计数为 1。所以不 gua运行 认为这只是测量中的一次错误计数,但可能不是一打。

总之,希望对你的理解有所帮助。