用于循环的矩阵乘法会降低性能吗

Will matrix multiplication using for loops decrease performance?

本文关键字:性能 循环 用于      更新时间:2023-10-16

目前我正在开发一个使用矩阵的程序。我想出了这个嵌套循环来乘以两个矩阵:

// The matrices are 1-dimensional arrays
for (int i = 0; i < 4; i++)
    for (int j = 0; j < 4; j++)
        for (int k = 0; k < 4; k++)
            result[i * 4 + j] += M1[i * 4 + k] * M2[k * 4 + j];

循环工作。我的问题是:与像这样手动写出所有内容相比,这个循环会更慢吗:

result[0] = M1[0]*M2[0] + M1[1]*M2[4] + M1[2]*M2[8] + M1[3]*M2[12];
result[1] = M1[0]*M2[1] + M1[1]*M2[5] + M1[2]*M2[9] + M1[4]*M2[13];
result[2] = ... etc.

因为在嵌套循环中,会计算数组位置,而在第二种方法中,则不会计算。

谢谢。

与许多事情一样,"这取决于",但在这种情况下,我倾向于使用第二种扩展形式来执行大致相同的功能。任何现代编译器都会为您展开适当的循环,并处理好它

两点也许值得一提:

  1. 第二种方法更丑陋,更容易出错,编写/维护起来也很乏味。

  2. 这是一个"过早优化"(又名万恶之源)的好例子。你知道这个部分是否是一个瓶颈吗?这真的是代码中最密集的部分吗?通过这么早的优化,如果我们没有对代码进行基准标记,我们会在第1点中产生所有的东西,这相当于一种预感。

您的编译器可能已经完成了这项工作,请查看循环展开。让编译器做猜测和繁重的工作,坚持干净的代码,并一如既往地衡量您的性能。

我认为循环不会变慢。您在两种情况下都以相同的方式访问M1和M2阵列的内存,即。如果您想使"手动"版本更快,请使用标量替换并在寄存器上进行计算,例如

 double M1_0 = M1[0];
 double M2_0 = M2[0];
 result[0] = M1_0*M2_0 + ...

但是您也可以在循环中使用标量替换。如果您进行阻塞和循环展开,就可以做到这一点(事实上,您的三重循环看起来像MMM的阻塞版本)。

您要做的是通过改进局部性来加快程序的速度,即更好地使用内存层次结构和更好的局部性。

假设您在英特尔处理器或兼容(AMD)上运行代码,您可能实际上想要切换到汇编语言来进行繁重的矩阵计算。幸运的是,您有"英特尔IPP"库,它可以使用先进的处理器技术为您完成实际工作,并根据您的处理器选择最快的算法。

IPP包括您可能需要的所有必要的矩阵计算。你可能遇到的唯一问题是你创建矩阵的顺序。您可能需要重新组织订单,以便更容易地使用您想要使用的IPP功能。

请注意,就您的两个代码示例而言,第二个示例会更快,因为您避免了+=运算符,这是一个读/修改/写周期,而且通常很慢(不仅如此,它还要求结果矩阵从零开始,而第二个实例不需要先清除输出),尽管您的矩阵可能适合缓存。。。但是,处理器被优化为按顺序读取输入数据(a[0],a1,a[2],a[3],…),并且还按顺序写回该数据。如果你能把你的算法写得尽可能接近这样一个序列,那就更好了。不要误解我的意思,我知道矩阵乘法不能按顺序进行。但是,如果你考虑这样做来进行优化,你会获得更好的结果(即,改变矩阵在内存中的保存顺序可能就是其中之一)。