【问题标题】:How does splitting a matrix in blocks improve cache hits?将矩阵拆分为块如何提高缓存命中率?
【发布时间】:2017-03-22 02:48:19
【问题描述】:

我正在尝试理解矩阵中的缓存和阻塞的概念。我正在尝试转置一个矩阵。

我了解按行内存布局的概念,因此当我尝试按行访问数据时,与按列相比,缓存未命中次数更少。

  for( int i = 0; i < n; i++ )

    for( int j = 0; j < n; j++ )

      destination[j+i*n] = source[i+j*n];

所以对于源矩阵,我将有更少的缓存未命中,而对于目标我将有更多的缓存未命中。


这是带有阻塞的代码

for (int i = 0; i < n; i += blocksize) {
    for (int j = 0; j < n; j += blocksize) {
        // transpose the block beginning at [i,j]
        for (int k = i; k < i + blocksize; ++k) {
            for (int l = j; l < j + blocksize; ++l) {
                dst[k + l*n] = src[l + k*n];
            }
        }
    }
}

上面的代码使用了阻塞技术。我不太明白屏蔽对性能有何帮助?

【问题讨论】:

  • 我不能声称理解使用缓存的算法性能优化,但 Demaine 教授有 quite a few 的主题讲座。也许他们会提供帮助。

标签: caching matrix


【解决方案1】:

是的,一侧的缓存命中率会比另一侧多。

不过,诀窍是将其分成足够小的部分,以便在处理时可以“重复使用”。

例如在上面的示例中,我们将在 src 矩阵上有 1 个缓存未命中,在 dst 大小上有 4 个(我选择了 4 个元素的缓存行大小和 4 个元素的块大小,但这只是巧合)。

如果缓存大小大于 5 行,我们在处理该行时将不再有未命中。

如果缓存大小小于该值,则会有更多的未命中,因为这些行将相互排挤。在这种情况下,src 将保留在缓存中,因为使用得更多,而 dst one 的将被丢弃,从而在 dst 端给我们 16 次未命中。 5 看起来比 17 好 :)

所以通过控制块的大小足够低,我们可以降低缓存未命中率。

【讨论】:

  • 你能解释一下我们所说的块大小是什么意思吗?缓存中实际有块,对吧?这只是我们的代表。
  • 块大小是您算法中的blocksize。这就是我们正在使用的子矩阵的大小缓存中的最小可缓存块称为缓存行。
【解决方案2】:

这里最重要的方面是locality,尤其是对于您的示例spatial locality。粗略地说,这意味着访问彼此接近的数据。

每当矩阵的一个元素被访问时,它还没有在缓存中,它总是加载整个缓存行。例如,该高速缓存行可以包含 64 字节或 16 个整数元素。如果您在缓存中不使用其他元素,则您的代码效率低下。

如果在两次访问之间加载了太多数据,缓存行将被逐出。考虑从加载source[i] 的内部循环的第一次访问。内存中的下一个元素source[i + 1] 位于同一缓存行上并且已经加载。但是,只有在整个内循环完成后才需要它。如果内部循环访问的数据过多,source[i + 1] 将不再在缓存中。访问source[i]source[i+1] 之间的距离太大。

阻塞消除了这种低效率。最里面的循环比原来小很多,访问之间的距离减少了。最内层循环处理的所有数据都适合缓存。

【讨论】:

    猜你喜欢
    • 2022-08-12
    • 1970-01-01
    • 1970-01-01
    • 2017-09-15
    • 2015-03-16
    • 2020-05-13
    • 1970-01-01
    • 2012-06-21
    • 1970-01-01
    相关资源
    最近更新 更多