这可能与语言无关,但我是从C ++背景中提问的。
我正在为一个嵌入式系统(AVR,8位)黑客攻击一个环形缓冲区。我们假设:
const uint8_t size = /* something > 0 */;
uint8_t buffer[size];
uint8_t write_pointer;
如果缓冲区的&
是2的幂,那么size-1
size
写入和读取指针与// value = buffer[write_pointer];
write_pointer = (write_pointer+1) & (size-1);
进行有效的无分支翻转这样的巧妙技巧: / p>
// value = buffer[write_pointer];
if (++write_pointer == size) write_pointer ^= write_pointer;
但是,如果大小不是2的幂,则回退可能是指针(即索引)与大小的比较,并进行条件重置:
size
由于重置很少发生,因此对于任何分支预测都应该很容易。
这假设指针需要在内存中向前推进。虽然这很直观,但在每次迭代中都需要jump if not zero
的负载。我假设在正常情况下,反转顺序(向前推进向后)会产生更好的CPU指令(即size
),因为在复位期间只需要// value = buffer[--write_pointer];
if (write_pointer == 0) write_pointer = size;
。
{{1}}
所以
TL; DR:我的问题是:由于缓存未命中(因为内存不能简单地向前读取),在内存中向后行进会对执行时间产生负面影响,还是这是一个有效的优化?
答案 0 :(得分:3)
你有一个带缓存的8位avr吗?分支预测?
就高速缓存而言,前进或后退有何影响?缓存中的命中或未命中是缓存行中的任何位置,开始,中间,结束,随机,顺序,无关紧要。您可以从高速缓存行的后面到前面或前后工作,这是相同的成本(假设所有其他事物保持不变)第一个雾导致填充,然后该行在缓存中,您可以访问任何在被驱逐之前,任何模式中的项目的延迟较低。
在像这样的微控制器上,你想要付出努力,即使以丢弃一些内存为代价,也可以对齐一个循环缓冲区,以便你可以屏蔽。没有缓存指令提取是痛苦的,因为它们可能来自可能比处理器时钟速率慢的闪存,因此您确实希望减少执行的指令,或使执行更加确定(每个循环的指令数相同)直到完成任务)。可能有一个管道会欣赏掩蔽,而不是if-then-else。
TL; DR:我的问题是:通过记忆向后行进有一个 由于缓存未命中对执行时间的负面影响(因为 内存不能简单地向前读取)或者这是一个有效的优化吗?
缓存并不关心,线路中任何项目的遗漏都会导致填充,一旦进入缓存,任何访问模式,随机,顺序前进或后退,或只是敲击同一地址,花费更少的时间更快记忆。直到被驱逐。驱逐不会来自相邻缓存线,它们将来自缓存线两个以上的较大功率,因此无论您下拉的下一个缓存线是在更高的地址还是更低,成本都是相同的。
答案 1 :(得分:0)
通过记忆向后行进会对此产生负面影响 由于缓存未命中导致的执行时间(因为不能简单地读取内存) 前进)
为什么你认为你会错过缓存?如果您尝试在缓存外部(向前或向后)访问,则会出现缓存未命中。
答案 2 :(得分:0)
有许多要点需要澄清:
0' is a valid pointer into the buffer, and the value
size'不是。同样地,当您只需分配xor
时,就不需要0
,同样模数运算符也可以工作(writer_pointer = (write_pointer +1) % size
)。简而言之:这是一种优化,导致更多的脚受伤而不是真正的性能提升。此外,几乎可以肯定的是,使用矢量化代码(SIMD)可以获得更多,更好的收益。
编辑:在解释型语言或JIT语言中,假设您可以完全依赖JNZ
和其他人的使用,可能会有点乐观。问题是,loading size
与比较与0
之间存在多大差异。
答案 3 :(得分:0)
像往常一样,在执行任何形式的手动代码优化时,您必须对特定硬件有广泛的深入了解。如果您没有,那么您不应该尝试手动优化,故事结束。
因此,你的问题充满了各种奇怪的假设:
首先,您假设write_pointer = (write_pointer+1) & (size-1)
比其他内容更有效,例如您发布的XOR示例。你只是在这里猜测,你将不得不反汇编代码,看看哪些产生的CPU指令就越少。
因为,当为一个微小的,原始的8位MCU编写代码时,核心中没有太多东西可以加速你的代码。我不知道AVR8,但似乎你有一个小的指令管道就是这样。你似乎不太可能对分支预测有很大帮助。您似乎非常不太可能拥有数据和/或指令缓存。阅读友好的CPU核心手册。
至于通过记忆向后行进,它不会对你的程序性能产生任何影响。在旧的,糟糕的编译器上,如果循环条件是比较而不是值,则会获得稍微更高效的代码。在现代编译器上,这应该不是问题。至于缓存问题,我怀疑你有任何缓存需要担心。
在8位MCU上编写高效代码的最佳方法是尽可能坚持使用8位算法,并避免像瘟疫这样的32位算术。忘了你曾经听过一个叫浮点的东西。 此将使您的计划更有效率,您不太可能找到更好的方法来手动优化您的代码。