c-缓存阻塞实际上是如何提高性能的



我在这个intel页面上读到了关于缓存阻塞的文章。

上面写着:

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

提供示例

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的因子。

老实说,我不理解附带的解释。

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

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


维基百科也有一个类似的例子:

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

可以用替换块大小为B的块

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

我不知道为什么这会更快。

为什么要利用缓存?

那篇英特尔论文很糟糕,因为它没有弄清楚索引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.

通过在改变CCD_ 14之前处理较少的CCD_。如果BLOCK被设置为足够小,则与OUT[body1]相关联的高速缓存块仍在高速缓存中,并且不需要从存储器重新加载。因此,在内部循环期间:

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

用于CCD_ 17的每个高速缓存块仅从存储器加载一次并且写入存储器一次。

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

最新更新