【问题标题】:Is a binary operation faster than memmove?二进制操作比 memmove 快吗?
【发布时间】:2013-09-04 11:16:01
【问题描述】:

我正在编写一个数字滤波器,我需要保留最后的 X 值并将它们加在一起。

现在有两种可能的方法。要么我使用memmove 移动整个数组,以便为​​下一个值腾出空间,并在我的求和算法中将数组的正确索引作为硬编码值。

memmove(&Fifo[0], &Fifo[1], 12 * 4); // Shift array to the left

Result += Factor[1] * (Fifo[5] + Fifo[7]);
Result += Factor[2] * (Fifo[4] + Fifo[8]);
Result += Factor[3] * (Fifo[3] + Fifo[9]);
Result += Factor[4] * (Fifo[2] + Fifo[10]);
Result += Factor[5] * (Fifo[1] + Fifo[11]);
Result += Factor[6] * (Fifo[0] + Fifo[12]);

或者,我不复制任何内存,而是增加一个计数器,并使用模运算(如循环缓冲区)从中计算每个索引。

i++; // Increment the index

Result += Factor[1] * (Fifo[(i + 5) % 13] + Fifo[(i + 7) % 13]);
Result += Factor[2] * (Fifo[(i + 4) % 13] + Fifo[(i + 8) % 13]);
Result += Factor[3] * (Fifo[(i + 3) % 13] + Fifo[(i + 9) % 13]);
Result += Factor[4] * (Fifo[(i + 2) % 13] + Fifo[(i + 10) % 13]);
Result += Factor[5] * (Fifo[(i + 1) % 13] + Fifo[(i + 11) % 13]);
Result += Factor[6] * (Fifo[(i + 0) % 13] + Fifo[(i + 12) % 13]);

由于它是嵌入式 ARM cpu,我想知道什么会更有效。由于我假设 CPU 必须在内部至少移动一个 32 位值才能进行模运算,难道仅仅移动整个数组就与计算正确的索引一样快吗?

【问题讨论】:

  • 编写两个解决方案,然后对它们进行基准测试。
  • @JoachimPileborg Benchmarking 只会告诉我哪种方法更快,但我想了解为什么它会更快。
  • 为此,您可能需要查看生成的代码。还要记住,与其他指令相比,函数调用可能会很昂贵。
  • @JoachimPileborg 不幸的是,我不是装配专家。
  • 原因取决于您的编译器及其输出。此时您可能只需要成为装配新手即可了解具体细节。

标签: c performance embedded arm memcpy


【解决方案1】:

如果您需要知道哪个更快,您需要进行基准测试。如果你想 知道为什么,您需要检查程序集。

话虽如此,还有一个中途解决方案可能已经足够好了: 使用大于需要的缓冲区,并且仅在缓冲区已满时才执行memmove。 这样您只需要跟踪起始偏移量,而不必担心 关于循环缓冲区带来的问题。但是你必须使用更多的内存。

因此,如果您希望拥有 5 个元素并为 10 个元素使用缓冲区,那么您只有 每 5 次插入执行memmove。 (除了可以插入 10 次的第一遍)

【讨论】:

    【解决方案2】:

    我已经在 Cortex M0 (LPC11C14) 上为尺寸为 15 的 FIR 滤波器(用于测量线路电压的 Savitzky-Golay)完成了这一工作。

    我发现在我的情况下,复制比使用大小为 16 的循环缓冲区和使用模运算符计算索引要慢一些。请注意,16 是 2 的幂,这使得除法非常便宜。

    我尝试了几种变体并使用端口引脚来测量执行时间,我建议您也这样做。

    【讨论】:

    • 在大多数情况下,在 y 是 2 的幂次方的情况下执行 x mod y 会被处理为 x AND (y - 1),这确实很便宜。
    • 我也是这么想的,但是发现编译成左移后右移,所以最高位掉了。
    【解决方案3】:

    假设 32 位值,ARM 上的 Modulo 可以在 2 条汇编指令中执行,但移动内存也是如此(1 将其放入寄存器,1 将其取出)。所以这里没有明确的答案;这将取决于它周围的代码。

    我的直觉告诉你应该采用循环缓冲区方法。

    【讨论】:

      【解决方案4】:

      第三种方式既不需要 memmove 也不需要涉及两个 switch 块的模。我懒得打字了,但想法是你计算偏移量,使用第一个开关计算缓冲区的“一半”,然后重新计算偏移量并使用第二个开关计算另一半缓冲。您基本上输入第二个开关,第一个开关“离开”。请注意,在一个 switch 块中,指令顺序必须恢复。

      【讨论】:

        【解决方案5】:

        我的直觉是 memmove 可能会导致各种内存冲突并防止内部绕过,因为您加载和存储到相同的区域,甚至可能是相同的缓存行。一些处理器会简单地放弃优化这一点并推迟所有内存操作,有效地序列化它们(嵌入式 CPU 无论如何可能足够简单,但我说的是一般情况 - 在 x86 甚至 cortex a15 上你可能获得更大的惩罚)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-04-21
          • 2020-03-12
          • 2014-01-07
          • 1970-01-01
          • 2011-10-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多