为什么 2048x2048 与 2047x2047 数组乘法相比,性能会受到巨大影响?

2024-03-02

我正在做一些矩阵乘法基准测试,如前面提到的为什么 MATLAB 的矩阵乘法如此快? https://stackoverflow.com/questions/6058139/why-is-matlab-so-fast-in-matrix-multiplication

现在我遇到了另一个问题,当将两个 2048x2048 矩阵相乘时,C# 和其他矩阵之间存在很大差异。当我尝试仅乘以 2047x2047 矩阵时,这似乎很正常。还添加了一些其他内容进行比较。

1024x1024 - 10 秒。

1027x1027 - 10 秒。

2047x2047 - 90 秒。

2048x2048 - 300 秒。

2049x2049 - 91 秒。 (更新)

2500x2500 - 166 秒

对于 2k x 2k 的情况来说,存在三分半钟的差异。

使用 2dim 数组

//Array init like this
int rozmer = 2048;
float[,] matice = new float[rozmer, rozmer];

//Main multiply code
for(int j = 0; j < rozmer; j++)
{
   for (int k = 0; k < rozmer; k++)
   {
     float temp = 0;
     for (int m = 0; m < rozmer; m++)
     {
       temp = temp + matice1[j,m] * matice2[m,k];
     }
     matice3[j, k] = temp;
   }
 }

这可能与 L2 缓存中的冲突有关。

matice1 上的缓存未命中不是问题,因为它们是按顺序访问的。 然而,对于 matice2,如果完整的列适合 L2 (即当您访问 matice2[0, 0]、matice2[1, 0]、matice2[2, 0] ...等时,没有任何内容被驱逐),那么没有问题matice2 的缓存也未命中。

现在要更深入地了解缓存的工作原理,如果变量的字节地址是 X,那么它的缓存行将是 (X >> 6) & (L - 1)。其中 L 是缓存中缓存行的总数。 L 始终是 2 的幂。 这 6 个事实是因为 2^6 == 64 字节是缓存行的标准大小。

现在这意味着什么?这意味着如果我有地址 X 和地址 Y 并且 (X >> 6) - (Y >> 6) 可以被 L(即 2 的某个大幂)整除,它们将存储在同一个缓存行中。

现在回到你的问题 2048 年和 2049 年有什么区别,

当 2048 是你的尺寸时:

如果您采用 &matice2[x, k] 和 &matice2[y, k] 差值 (&matice2[x, k] >> 6) - (&matice2[y,k] >> 6) 将被 2048 * 4 整除(大小的浮动)。所以是2的大幂。

因此,根据 L2 的大小,您将遇到很多缓存行冲突,并且仅利用 L2 的一小部分来存储列,因此您实际上无法在缓存中存储完整的列,因此您的性能会很差。

当大小为 2049 时,差异为 2049 * 4,它不是 2 的幂,因此您的冲突会更少,并且您的列将安全地适合您的缓存。

现在为了检验这个理论,你可以做几件事:

像这样 matice2 [razmor, 4096] 分配您的数组 matice2 数组,并以 razmor = 1024、1025 或任何大小运行,您应该会看到与之前相比非常糟糕的性能。这是因为您强制对齐所有列以使其相互冲突。

然后尝试 matice2 [razmor, 4097] 并以任何大小运行它,您应该会看到更好的性能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 2048x2048 与 2047x2047 数组乘法相比,性能会受到巨大影响? 的相关文章

随机推荐