C++中对矩阵进行缓存友好的C++操作

cache friendly C++ operation on matrix in C++?

本文关键字:C++ 操作 缓存      更新时间:2023-10-16

我的应用程序对大矩阵执行一些操作。我最近遇到了缓存的概念以及它通过这个答案可以产生的性能影响。我想知道对我的情况缓存友好的最佳算法是什么。

Algorithm 1:
for(int i = 0; i < size; i++)
{
    for(int j = i + 1; j < size; j++)
    {
        c[i][j] -= K * c[j][j];//K is a constant double variable
    }//c is a 2 dimensional array of double variables
}
Algorithm 2:
double *A = new double[size];
for(int n = 0; n < size; n++)
    A[n] = c[n][n];
for(int i = 0; i < size; i++)
{
    for(int j = i + 1; j < size; j++)
    {
        c[i][j] -= K * A[j];
    }
}

我的数组大小超过 1000x1000。在我的笔记本电脑上进行基准测试显示,对于 5000x5000 的大小,算法 2 优于 1。请注意,我已经对我的应用程序进行了多线程处理,以便一组行由线程操作。

For example: For array of size 1000x1000.
thread1 -> row 0 to row 249
thread2 -> row 250 to row 499
thread3 -> row 500 to row 749
thread4 -> row 750 to row 999

如果您的基准测试显示第二种情况有显著改善,那么它很可能是更好的选择。但是,当然,要知道"平均CPU",我们必须知道,对于可以称为平均的大量CPU,没有其他方法。这实际上取决于平均CPU的定义。我们是在谈论"任何x86(AMD + Intel)CPU"还是"我们可以在从手表到x86系列中最新的超快速创作中找到的任何随机CPU"?

"复制c[n][n]中的数据"方法很有帮助,因为它获得了自己的地址,并且当代码在更大的矩阵上行走时不会被抛出(L1)缓存[并且乘法所需的所有数据都是"紧密在一起的"。如果你走c[j][j],每j步骤每次迭代都会跳sizeof(double) * (size * j + 1)字节,所以如果大小大于 4,则需要的下一项不会在同一缓存行中,因此需要另一个内存读取来获取该数据。

换句话说,对于任何具有体面大小的缓存(大于size * sizeof(double))的任何内容,这是一个明确的好处。即使缓存较小,也很可能有一些好处,但缓存副本被c[i][j]的某些部分丢弃的可能性更高。

总之,第二种算法很可能对几乎所有选项都更好。

算法 2 受益于所谓的"空间局部性",将对角线移动到单维数组中使其以连续地址的形式驻留在内存中,从而:

  1. 享受每个缓存行获取多个有用元素的好处(大概是 64 字节,具体取决于您的 CPU),更好地利用缓存和内存 BW(而 c[n][n] 也会获取大量无用的数据,因为它在同一行中)。

  2. 享受硬件流预取程序(假设您的 CPU 中存在)的好处,它沿着页面在代码之前积极运行,并将数据提前带到较低的缓存级别,从而改善内存延迟。

应该指出的是,将数据移动到 A 并不一定能提高可缓存性,因为 A 仍然会与不断来自 c 的大量数据竞争并破坏缓存。但是,由于它被反复使用,因此一个好的 LRU 算法很有可能会让它留在缓存中。您可以通过对数组 c 使用流内存操作来提供帮助。应该注意的是,这些是非常不稳定的性能工具,如果使用不当,在某些情况下可能会导致性能降低。

另一个潜在的好处可能是在到达每个新数组行之前稍微混合软件预取。