assembly - 最快的轮询循环 - 如何修剪 1 个 CPU 周期?

标签 assembly micro-optimization cortex-m3

在 ARM Cortex M3(类似于 STM32F101)上的实时应用程序¹中,我需要在尽可能紧密的循环中轮询一些内部外设寄存器直到它为零。我使用位带来访问适当的位。 (工作)C 代码是

while (*(volatile uint32_t*)kMyBit != 0);

该代码被复制到片上可执行 RAM 中。经过一些手动优化²,轮询循环下降到以下,我将³计时为 6 个周期:
0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 2A00      CMP      r2,#0x00
0x00600204 D1FC      BNE      0x00600200

如何降低投票的不确定性? 一个 5 个周期的循环符合我的目标:在它变为零后尽可能接近 15.5 个周期对同一位进行采样。

我的规范要求可靠地检测至少 6.5 个 CPU 时钟周期的低脉冲;如果持续时间少于 12.5 个周期,则可靠地将其归类为短;如果它持续超过 18.5 个周期,就可以可靠地对其进行分类。脉冲与 CPU 时钟没有确定的相位关系,这是我唯一准确的时序引用。这需要最多 5 个时钟的轮询循环。实际上,我正在模拟在几十年前的 8 位 CPU 上运行的代码,该 CPU 可以以 5 个时钟周期进行轮询,而这已成为规范。

我试图通过在循环之前插入 NOP 来抵消代码对齐,在我尝试过的许多变体中,但从未观察到任何变化。

我试图反转 CMP 和 LDR,但仍然得到 6 个周期:
0x00600200 681A      LDR      r2,[r3,#0x00]
; we loop here
0x00600202 2A00      CMP      r2,#0x00
0x00600204 681A      LDR      r2,[r3,#0x00]
0x00600206 D1FC      BNE      0x00600202

这个是8个周期
0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 681A      LDR      r2,[r3,#0x00]
0x00600204 2A00      CMP      r2,#0x00
0x00600206 D1FB      BNE      0x00600200

但这是9个周期:
0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 2A00      CMP      r2,#0x00
0x00600204 681A      LDR      r2,[r3,#0x00]
0x00600206 D1FB      BNE      0x00600200

¹ 在没有中断发生的情况下测量该位处于低电平的时间。

² 最初的编译器生成的代码使用 r12 作为目标寄存器,并在循环中添加了 4 个代码字节,花费了 1 个周期。

³ 给出的数字是通过所谓的周期精确实时 STIce emulator 获得的及其在寄存器地址读取时的仿真器触发功能。以前我在循环中尝试了带有断点的“States”计数器,但结果取决于断点的位置。单步更糟:它总是为 LDR 提供 4 个周期,但至少有时会降到 3 个。

最佳答案

如果我正确理解了这个问题,那么需要减少的不一定是循环周期,而是后续样本(即 LDR 指令)之间的周期数。但是每次迭代可以有多个 LDR。你可以尝试这样的事情:

    ldrb    r1, [r0]

loop:
    cbz     r1, out
    ldrb    r2, [r0]
    cbz     r2, out
    ldrb    r1, [r0]
    b       loop

out:

两条 LDRB 指令之间的间距不同,因此样本的间距不均匀。

这可能会稍微延迟退出循环,但从问题描述我不能说它是否重要。

我碰巧可以访问周期精确的 M7 模型,当过程稳定时,您的原始循环在每次迭代 3 个周期内在 M7 上运行(意味着 LDR 每 3 个周期),而上面建议的循环在 4 个周期内运行,但现在有那里有两个 LDR(所以 LDR 每 2 个周期)。采样率肯定会提高。

值得称赞的是,@Peter Cordes in a comment 建议以 CBZ 作为休息时间展开。 .

诚然,M3 会更慢,但如果它是您所追求的采样率,它仍然值得一试。

此外,您还可以检查 LDRB 而不是 LDR(如上面的代码所示)是否会改变任何内容,尽管我不希望它会改变。

UPD:我有另一个 2-LDR 循环版本,它在 M7 上在 3 个周期内完成,您可以根据兴趣尝试(CBZ 中断也允许在循环后轻松平衡路径):
    ldr     r1, [r0]

loop:
    ldr     r2, [r0]
    cbz     r1, out_slow
    cbz     r2, out_fast
    ldr     r1, [r0]
    b       loop

out_fast:
    /* NOPs as required */

out_slow:

关于assembly - 最快的轮询循环 - 如何修剪 1 个 CPU 周期?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59790885/

相关文章:

c - 开关盒 assembly 级代码

javascript - LMC 改变 y 值

assembly - 分配给具有特定索引的位的好方法是什么?

linux - 如何在 ARM Cortex M3 开发板上运行 Linux?

memory - 中断向量中中断处理程序的地址为实际地址的+1

assembly - 澄清 32 位寄存器 x86 的各个部分

debugging - EBP 寄存器(基帧指针)是否仅用于 x86 中的调试?

algorithm - 对对进行排序以使其更加连续

php - 应该使用 `if ($a != NULL)` 还是 `if ($a !== NULL)` 来控制程序流?

c++ - Freertos 硬故障分析——堆栈寄存器