次优缓存行预取的成本

时间:2019-02-22 07:11:50

标签: c++ performance assembly x86 prefetch

使用DataModel dataModel; public MainPage() { InitializeComponent(); dataModel = new DataModel(); EmployeeView.ItemsSource = dataModel.employees; } void OnButtonClicked(object sender, EventArgs args) { Console.WriteLine("click me"); dataModel.employees.Add(new Employee { DisplayName = EntryText.Text }); } 内在函数(为准备写操作而进行的预取)进行后期预取的成本是多少?就是说,预取在需求加载或需要它的写入之前没有到达L1高速缓存?

例如

__builtin_prefetch(..., 1)

在这种情况下,如果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) } 的成本低于预取,那么此代码的效率最终会比没有预取时低吗?缓存预取中的wikipedia article讨论了for循环的最佳步幅,但没有提及次佳预取在这种情况下的影响(例如,如果k太低会发生什么?)。

这是否足够流水线以至于次优预取没有关系?对于这个问题,我仅考虑使用Intel x86(也许是在Broadwell时代的处理器)。

1 个答案:

答案 0 :(得分:6)

让我们将您所指的预取类型称为 late 预取:在这种情况下,预取在使用相同缓存行来完全隐藏请求的延迟或需求之前完全没有发生。缓存未命中。这与太早预取相反,在该预取中,预取发生在与需求访问相距太远的地方,以至于在发生访问之前,它已从至少某些级别的缓存中逐出。

与根本不进行预取相比,后期预取的成本可能很小,为零或为负

让我们专注于负面方面:即,即使预取时间很晚,预取还是有帮助的情况。如果我正确理解了您的问题,则认为预取不会在需要它的负载“丢失”或无效之前到达。但是,情况并非如此:一旦预取请求开始,时钟就开始滴答作响,以完成内存访问;如果需求负载在完成之前发生,则工作不会丢失。例如,如果您的内存访问时间为100 ns,而需求访问仅在预取后20 ns发生,则预取是“太迟了”,即未隐藏全部100 ns的延迟,而是20 ns花费在预取仍然有用:它将需求访问延迟减少到大约80 ns。

也就是说, late 预取不是二进制条件:它的发生范围从稍晚(例如,预取在访问前90 ns发出,等待时间为100 ns),或者真的很晚(几乎在消费访问之前)。在大多数情况下,假设内存延迟首先是您算法的瓶颈,那么即使是相当晚的预取也可能会有所帮助。

费用

现在让我们考虑完全无用的预取的情况(即在访问之前立即发出,因此如果不存在预取,则可以在原位置发出访问)-成本是多少?在最现实的情况下,成本可能很小:要处理额外的指令,对AGU施加一些小的额外压力,以及在将后续访问与进行中的预取 2 <相匹配时,可能会浪费少量的精力/ sup>。

由于假设是由于错过了高速缓存或DRAM的外部级别而使用了预取,并且transform函数中的工作足以掩盖某些延迟,因此相对成本另外一条指令可能很小。

当然,所有这些都是在附加预取是一条指令的前提下进行的。在某些情况下,您可能不得不某种程度地组织代码以允许预取或执行一些重复的计算以允许在适当的位置进行预取。在这种情况下,成本方面可能会更高。

M和E国家

最后,在具有写意图的写访问和预取方面还有其他行为,这意味着在某些情况下,即使是完全无用的预取(即紧接在第一次访问之前)也很有用-当第一次访问是阅读。

如果先读取给定的行,然后再写入,则内核可能会在E(xclusive)coherence state中获得该行,然后在第一次需要对缓存的某个级别进行另一次往返来获取它处于M状态。在第一次访问之前使用具有写意图的预取将避免第二次往返,因为该行将在第一次进入M状态。通常,这种优化的效果很难量化,这不仅是因为写入通常是缓冲的,并且不构成依赖链的一部分(存储转发之外)。


2 我在这里使用了刻意模糊的术语“浪费的精力”,因为目前尚不清楚它是否具有性能或功耗成本,或者仅仅是一些没有增加的工作量操作延迟。一种可能的代价是,触发初始L1丢失的负载具有特殊状态,并且可以在不进行另一次L1往返的情况下接收其结果。在预取紧接着是负载的情况下,该负载大概没有处于特殊状态,这可能会稍微增加成本。但是,这个问题是关于存储而不是加载。