在L1和L2预取数据

18
在Agner Fog的手册优化C++软件第9.10节“大型数据结构中的高速缓存争用”中,他描述了一个问题:当矩阵宽度等于所谓的关键步幅时,转置矩阵的问题。在他的测试中,当宽度等于关键步幅时,L1中矩阵的成本比正常情况高40%。如果矩阵更大,只适合L2,则成本为600%!这在他的文本中的表格9.1中得到了很好的总结。这基本上是观察到的相同现象为什么转置512x512的矩阵比转置513x513的矩阵慢得多? 后来他写道:

这种效果对于二级高速缓存争用而言比一级高速缓存争用强得多的原因是,二级高速缓存无法预取超过一行。

我的问题与预取数据有关。

从他的评论中我推断出L1可以一次预取多个缓存行。它能预取多少个?

据我所知,尝试编写代码来预取数据(例如使用_mm_prefetch)很少有帮助。唯一我读过的例子是Prefetching Examples?,只有10%的改进(在某些机器上)。Agner后来解释了这一点:

原因是现代处理器通过乱序执行和先进的预测机制自动预取数据。现代微处理器能够自动为包含具有不同步长的多个流的常规访问模式预取数据。因此,如果数据访问可以按照固定步长的常规模式排列,则无需显式地预取数据。

那么CPU如何决定要预取哪些数据,是否有办法帮助CPU做出更好的预取选择(例如“具有固定步幅的常规模式”)?

编辑:根据Leeor的评论,让我补充我的问题并使其更加有趣。 为什么关键步幅对L2的影响要比对L1大得多?

编辑:我尝试使用为什么转置512x512的矩阵比转置513x513的矩阵慢得多?中的代码复制Agner Fog的表格。我在具有L1 32KB 8路、L2 256 KB 8路和L3 10MB 20路的Xeon E5 1620(常春藤桥)上以MSVC2013 64位发布模式运行了此代码。 L1的最大矩阵大小约为90x90,L3的最大矩阵大小为256x256,L3的最大矩阵大小为1619。

Matrix Size  Average Time
64x64        0.004251 0.004472 0.004412 (three times)
65x65        0.004422 0.004442 0.004632 (three times)
128x128      0.0409
129x129      0.0169
256x256      0.219   //max L2 matrix size
257x257      0.0692
512x512      2.701
513x513      0.649
1024x1024    12.8
1025x1025    10.1

我在L1中没有看到任何性能损失,但L2明显存在关键步幅问题,可能也存在于L3中。我还不确定为什么L1没有出现问题。可能有其他背景(开销)源支配着L1的时间。

1个回答

13

这个陈述是不正确的。

事实上,L2预取器通常比L1预取器更强更有攻击性。这取决于您使用的实际机器,但例如英特尔的L2预取器可以触发每个请求的2个预取,而L1通常受限制(L1中可能存在几种类型的预取,但它们可能会在比L2更有限的带宽上竞争,所以从L1中出来的预取可能会更少)。

在第2.3.5.4节(数据预取)中,优化指南列出了以下预取器类型:

Two hardware prefetchers load data to the L1 DCache:
- Data cache unit (DCU) prefetcher: This prefetcher, also known as the streaming prefetcher, is triggered by an ascending access to very recently loaded data. The processor assumes that this access is part of a streaming algorithm and automatically fetches the next line.
- Instruction pointer (IP)-based stride prefetcher: This prefetcher keeps track of individual load instructions. If a load instruction is detected to have a regular stride, then a prefetch is sent to the next address which is the sum of the current address and the stride. This prefetcher can prefetch forward or backward and can detect strides of up to 2K bytes.

 Data Prefetch to the L2 and Last Level Cache - 
 - Spatial Prefetcher: This prefetcher strives to complete every cache line fetched to  the L2 cache with the pair line that completes it to a 128-byte aligned chunk.
 - Streamer: This prefetcher monitors read requests from the L1 cache for ascending and descending sequences of addresses. Monitored read requests include L1 DCache requests initiated by load and store operations and by the hardware prefetchers, and L1 ICache requests for code fetch. When a forward or backward stream of requests is detected, the anticipated cache lines are prefetched. Prefetched cache lines must be in the same 4K page. 

稍微往前走一点:

... The streamer may issue two prefetch requests on every L2 lookup. The streamer can run up to 20 lines ahead of the load request.

以上提到的方案中,只有基于IP的方案可以处理大于一缓存行跨度的情况(流式方案可以处理任何连续缓存行使用的内容,即最多64字节的跨度(如果您不介意增加一些额外的行,则实际上最多可达到128字节)。要使用它,请确保在给定地址处进行的加载/存储将执行跨度访问-通常在循环遍历数组时已经是这种情况了。编译器循环展开可能会将其拆分成具有较大步长的多个不同步长的流,这样效果会更好(前瞻性会更大),除非您超出了已跟踪的IP数目-这又取决于具体的实现。

然而,如果您的访问模式确实包含连续线路,则L2流程比L1更高效,因为它可以更快地运行。


你能解释一下为什么关键步幅对L2性能的影响比L1大得多吗?这是600%(L2)与40%(L1)! - Z boson
这取决于具体情况,但有一点是肯定的,如果你遇到了关键步长问题,那么你很可能会适合进入下一个级别,而L1-->L2通常只需要增加几个周期,L2-->内存(如果该系统没有L3)在延迟比方面显著更差。 - Leeor
好的,我可以重现这个问题(顺便说一下,2048/2049也是如此,L3可能有相同的影响)。它也发生在640上 - 这是另一个集合数量的倍数。这表明您仍然受到关键步幅的影响。即使提前发送预取器,当您在BW上受到瓶颈限制时,预取器也永远无法替代良好的缓存,因为预取器只会使内存访问更早发生,而不是更快。最终,您的预取将滞后并阻塞您的实际访问。 - Leeor
我在我的问题中添加了一个不同矩阵大小和时间的表格。我还无法在L1中重现这个问题。 - Z boson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接