如何在时间复杂度方面优化cpp中的矩阵乘法?

时间:2016-04-07 18:55:14

标签: c++ matrix matrix-multiplication

给定任何2个matrics a和b(它们没有特殊属性)我们是否有更好的计算乘法的方法:?

for(i=0; i<r1; ++i)
for(j=0; j<c2; ++j)
for(k=0; k<c1; ++k)
{
    mult[i][j]+=a[i][k]*b[k][j];
}

5 个答案:

答案 0 :(得分:7)

如果你对它们理论上是否存在感到好奇,那么是的。例如,Strassen算法(参见https://en.wikipedia.org/wiki/Strassen_algorithm)。它甚至不是我们所知道的最快的。至于我现在最关心的是Coppersmith-Winograd算法(见https://en.wikipedia.org/wiki/Coppersmith%E2%80%93Winograd_algorithm),它类似于O(n^{2.37})(Strassen的时间复杂度类似{{1} }}

但实际上它们比你编写的那个更难实现,并且它们在O(n^{2.8})下隐藏了相当大的时间常数,所以你写的O()算法在{O(n^3)的低值时更好。 1}}并且更容易实现。

还有一个Strassen的假设声称,对于每个n,都有一个算法将两个矩阵与时间复杂度eps > 0相乘。但是你可能已经注意到它现在只是一个假设。

答案 1 :(得分:3)

作为一种非常简单的解决方案,您可以在乘法之前转置第二个矩阵,这样您的代码将获得更少的处理器缓存未命中。复杂性将是相同的,但它可能会稍微改善时间常数。

答案 2 :(得分:2)

这是世界上许多聪明人在你面前解决的问题。不要折磨自己并使用BLAS?GEMM。

http://www.netlib.org/blas/#_level_3

答案 3 :(得分:1)

这是一个很好的问题,应该比“使用图书馆”更完整的答案。

当然,如果你想做得好,你可能不应该尝试自己写。但如果这个问题是关于学习如何更快地进行矩阵乘法,那么这是一个完整的答案。

  1. 实际上,您展示的代码会过多地写入内存。如果内部循环在标量变量中添加点积,那么只在末尾写入,代码会更快。大多数编译器都不够聪明,无法理解这一点。

    双点= 0; for(k = 0; k

  2. 这也提高了多核性能,因为如果使用多个内核,则必须共享内存带宽。 如果您使用的是行数组,请将表示形式切换为单个内存块。

    1. 如上所述,您可以进行转置,因此矩阵遍历都是按顺序进行的。内存被设计为按顺序有效读取,但是你的b [k] [j]跳跃,所以这大约快3倍,因为大小变大(大约1000x1000,初始转置的成本可以忽略不计)

    2. 当矩阵变得足够大时,Strassen和Coppersmith-Winograd是更快的乘法方式,从根本上改变了规则,但是他们通过巧妙地重新排列术语来实现相同的理论结果,并且复杂度更低。在实践中,他们改变了答案,因为舍入误差是不同的,对于大型矩阵,这些算法产生的答案可能比蛮力乘法更糟糕。

    3. 如果您有一台真正的并行计算机,您可以将矩阵复制到多个CPU并让它们并行处理答案。

    4. 您可以将代码放到视频卡上,并使用那些具有更多内存带宽的并行CPU。这可能是在您的计算机上获得真正加速的最有效方法(假设您有一个显卡)。见CUDA或Vulkan。

    5. 基本问题是多核对矩阵乘法没有多大帮助,因为你受内存带宽的限制。这就是为什么在显卡上做这么好,因为那里的带宽要高得多。

答案 4 :(得分:0)

您可以通过将乘法除以它们来使用多个线程。因此,将第一个矩阵的第一个维度的行/列或者最后一个维度的行/列分成多个任务,这些任务等于处理器中的核心数。如果这些不可分割,则某些核心必须进行额外的循环。但无论如何,这个想法是给多个核心增加乘法,例如4个部分中的第一个矩阵(我有4个核心),用4个任务进行乘法运算,然后重新组合(这是不必要的,因为核心可以处理相同的数据)。