【问题标题】:How does cache blocking actually speed up performance?缓存阻塞实际上是如何提高性能的?
【发布时间】:2020-12-16 05:30:24
【问题描述】:

我正在阅读关于 this intel page 的缓存阻塞。

上面写着:

阻塞是一种众所周知的优化技术,可以帮助避免许多应用程序中的内存带宽瓶颈。阻塞背后的关键思想是通过确保数据在多次使用中保留在缓存中来利用应用程序中可用的固有数据重用。

举个例子

for (body1 = 0; body1 < NBODIES; body1 ++) {
   for (body2=0; body2 < NBODIES; body2++) {
     OUT[body1] += compute(body1, body2);
   }
}

在此示例中,数据 (body2) 从内存中流式传输。假设 NBODIES 很大,我们将无法在缓存中重用。此应用程序受内存带宽限制。应用程序将以内存速度运行到 CPU 速度,低于最佳速度。

优化到

for (body2 = 0; body2 < NBODIES; body2 += BLOCK) {
   for (body1=0; body1 < NBODIES; body1 ++) {
      for (body22=0; body22 < BLOCK; body22 ++) {
         OUT[body1] += compute(body1, body2 + body22);
      }
   }
}

被阻塞的代码...是通过将body2循环拆分为一个在多个BLOCK中迭代体的外循环和一个在块内迭代元素的内body22循环,并交错body1和body2循环。此代码在 body1 循环的多次迭代中重复使用一组 BLOCK body2 值。如果选择 BLOCK 以使这组值适合缓存,则内存流量会减少一个 BLOCK 系数。

老实说,我不明白随附的解释。

我也不明白内存流量是如何降低的,以及为什么被阻止的示例会更快。

请有人帮忙解释一下缓存阻塞以及它实际上是如何加速循环的?


Wikipedia同样有一个例子:

for (i=0; i<N; ++i) {
  ...
}

可以通过替换为块大小 B 来阻止

for (j=0; j<N; j+=B) {
  for (i=j; i<min(N, j+B); ++i) {
    ....
  }
}

我不知道为什么会更快。

为什么这会利用缓存?

【问题讨论】:

    标签: c caching optimization memory


    【解决方案1】:

    那篇英特尔论文很糟糕,因为它没有明确索引body2 与内存中数据所在位置之间的关联,或者body1 与内存中数据之间的关联。这个想法是OUT[body1] 将使用来自同一缓存块的多个元素来处理body1 的几个连续值。但是,body1 的连续值的使用不会一个接一个地发生。它们散开是因为body2 上的整个循环在它们之间执行。据推测,使用compute(body1, body2) 会导致根据body2 值使用各种内存。

    概念是,在body2 循环的过程中,内存中的许多不同位置将被访问,以至于与OUT[body1] 关联的缓存块将从缓存中逐出,因此,当body1 递增时到下一个值,但OUT[body1] 仍然在同一个缓存块中,该块不再在缓存中,必须再次加载。所以这很浪费。

    将源代码更改为:

    for (body2 = 0; body2 < NBODIES; body2 += BLOCK) {
       for (body1=0; body1 < NBODIES; body1 ++) {
          for (body22=0; body22 < BLOCK; body22 ++) {
             OUT[body1] += compute(body1, body2 + body22);
          }}
    // Note: A closing "}" is missing in the original.
    

    通过在更改body1 之前处理更少的compute(body1, something) 实例来解决该问题。如果BLOCK 设置得足够小,则与OUT[body1] 关联的缓存块仍在缓存中,不需要从内存中重新加载。因此,在内部循环中:

       for (body1=0; body1 < NBODIES; body1 ++) {
          for (body22=0; body22 < BLOCK; body22 ++) {
             OUT[body1] += compute(body1, body2 + body22);
          }}
    

    OUT[body1] 的每个缓存块仅从内存中加载一次并写入内存一次。

    当然,这些内循环在body2 上的新外循环内,因此内循环将被执行NBODIES/BLOCK 次(本文忽略了如果NOBODIES 不能被@987654343 整除时出现的块碎片@,我也会),所以OUT[body1]的缓存块仍然需要重新加载NBODIES/BLOCK次,但这比重新加载它们要好NBODIES次,这发生在原始代码中。

    【讨论】:

      猜你喜欢
      • 2018-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多