使用__builtin_prefetch(..., 1)
内在函数进行预取的费用是多少(预取为准备写操作)?就是说,在需求加载或需要它的写入之前没有到达L1高速缓存的预取?
例如
void foo(std::uint8_t* line) {
__builtin_prefetch(line + std::hardware_constructive_interference_size, 1);
auto next_line = calculate_address_of_next_line(line);
auto result = transform(line);
write(next_line, result)
}
在这种情况下,如果
transform
的成本低于预取,那么此代码的效率最终会比没有预取时低吗?缓存预取中的wikipedia article讨论了for循环的最佳步幅,但没有提及次佳预取在这种情况下的影响(例如,如果k太低会发生什么?)。这样是否流水线足够多,以至于次优预取并不重要?为了这个问题,我只考虑使用Intel x86(也许是在Broadwell时代的处理器)。
最佳答案
让我们将您称为延迟预取的类型称为预取:在预取未充分发生之前,即使用相同的缓存行来完全隐藏缓存未命中的延迟的需求加载或存储之前。这与太早的预取相反,在预取中预取发生在与需求访问相距太远的地方,以至于在发生访问之前,它已从高速缓存的至少某些级别中逐出。
与根本不进行预取相比,这种延迟的预取的代价很可能是非常小,为零或为负。
让我们集中讨论负面方面:即,即使预取很晚,预取还是有帮助的情况。如果我正确理解了您的问题,则认为预取不会在需要它的负载“丢失”或无效之前到达。但是,情况并非如此:一旦预取请求开始,时钟就开始滴答作响,以完成内存访问;如果在完成请求之前发生了需求负载,那么工作就不会丢失。例如,如果您的内存访问时间为100 ns,而需求访问仅在预取后20 ns发生,则预取是“太迟了”,即未隐藏全部100 ns的延迟,而是20 ns花费在预取仍然有用:它将需求访问延迟减少到约80 ns。
也就是说,延迟预取不是二进制条件:范围从延迟(例如,在访问之前90 ns发出预取,延迟为100 ns)到真正延迟(几乎在消耗访问之前) 。在大多数情况下,假设内存延迟首先是您的算法的瓶颈,即使是相对较晚的预取也可能会有所帮助。
费用表
现在让我们考虑完全无用的预取的情况(即,在访问之前立即发出,因此如果不存在预取,则可以在原位置发出访问)-成本是多少?在最现实的情况下,成本可能很小:要处理一条额外的指令,给AGU带来一些小的额外压力,并且在将后续访问与进行中的预取相匹配时可能会浪费少量的精力2。
由于假设是由于错过了高速缓存或DRAM的外部级别而采用了预取,并且transform
函数中的工作足以掩盖某些延迟,因此这一额外指令的相对成本很可能是很小。
当然,这全部是在附加预取是一条指令的前提下进行的。在某些情况下,您可能不得不某种程度地组织代码以允许预取或执行一些重复的计算以允许在适当的位置进行预取。在这种情况下,成本方面可能会更高。
M和E州
最后,在具有写意图的写访问和预取方面还有其他行为,这意味着在某些情况下,即使是完全无用的预取(即紧接在第一次访问之前)也很有用-当第一次访问是读操作时。
如果先读取给定的行,然后再写入,则内核可以将该行保存在E(xclusive)coherence state中,然后在第一次需要对高速缓存的某个级别进行另一次往返时,才能使其处于M状态。在第一次访问之前使用具有写意图的预取将避免第二次往返,因为该行将在第一次以M状态进入。通常,这种优化的效果很难量化,这不仅是因为写入通常是缓冲的,并且不构成依赖链的一部分(存储转发之外)。
2我在这里使用故意含糊的术语“浪费的精力”是因为它并不确定性能或功耗成本,还是仅仅是一些不会增加操作延迟的额外工作。一种可能的代价是,触发初始L1丢失的负载具有特殊状态,并且可以在不进行另一次L1往返的情况下接收其结果。在预取紧接着是负载的情况下,该负载可能没有处于特殊状态,这可能会稍微增加成本。但是,此问题与存储而不是加载有关。
关于c++ - 次优缓存行预取的成本,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54821849/