为什么我的斯特拉森矩阵乘法很慢?

Why is my Strassen's Matrix Multiplication slow?

本文关键字:我的 斯特拉 为什么      更新时间:2023-10-16

我用C++编写了两个矩阵乘法程序:正则MM(源代码)和Strassen的MM(源),这两个程序都对大小为2^k x 2^k的方阵(换句话说,偶数大小的方阵)进行运算。

结果太糟糕了。对于1024 x 1024矩阵,Regular MM取46.381 sec,而Strassen的MM取1484.303 sec(25 minutes!!!)。

我试图使代码尽可能简单。在网上找到的其他Strassen的MM示例与我的代码没有太大区别。Strassen代码的一个问题是显而易见的——我没有截止点,它切换到常规MM

我的Strassen MM代码还有什么其他问题???

谢谢!

直接链接到源
http://pastebin.com/HqHtFpq9
http://pastebin.com/USRQ5tuy

编辑1。拳头,很多好建议。感谢您慢慢来分享知识。

我实现了更改(保留了我所有的代码),添加了截止点。2048x2048矩阵的MM,带截止512已经给出了很好的结果。普通MM:191.49Strassen's MM:12.1179秒显著改进。结果是使用Visual Studio 2012在使用英特尔迅驰处理器的史前联想X61 TabletPC上获得的。我会做更多的检查(以确保我得到了正确的结果),并将公布结果。

Strassen代码的一个问题很明显——我没有截止点,切换到常规MM.

可以公平地说,递归到1点是大部分(如果不是全部的话)问题。尝试猜测其他性能瓶颈而不解决这一问题几乎是没有意义的,因为它会带来巨大的性能打击。(换句话说,你把苹果比作橙子。)

正如评论中所讨论的,缓存对齐可能会产生影响,但不会达到这种规模。此外,缓存对齐可能比Strassen算法对常规算法的伤害更大,因为后者是缓存遗忘的。

void strassen(int **a, int **b, int **c, int tam) {
// trivial case: when the matrix is 1 X 1:
if (tam == 1) {
c[0][0] = a[0][0] * b[0][0];
return;
}

那太小了。虽然Strassen算法的复杂度较小,但它的Big-O常数要大得多。首先,函数调用开销一直到1个元素。

这类似于使用merge或快速排序并一直递归到一个元素。为了提高效率,您需要在大小变小时停止递归,并返回到经典算法。

在快速/合并排序中,你会回到低开销的O(n^2)插入或选择排序。这里你会回到正常的O(n^3)矩阵乘法。


经典算法的阈值应该是一个可调的阈值,它可能会根据硬件和编译器优化代码的能力而变化。

对于Strassen乘法,其优势仅为O(2.8074)而不是经典的O(n^3),如果这个阈值非常高,不要感到惊讶。(数千个元素?)


在一些应用程序中,可能有许多算法,每个算法的复杂性都在降低,但Big-O却在增加。结果是,多个算法在不同的大小下变得最优。

大整数乘法就是一个臭名昭著的例子:

  • 小学乘法:O(N^2)对于<100位*
  • Karatsuba乘法:O(N^1.585)在约100位时比以上更快*
  • Toom Cook 3路:O(N^1.465)比Karatsuba快3000位*
  • 浮点FFT:O(>N log(N))比Karatsuba/Toom-3快约700位*
  • Schönhage–Strassen算法(SSA):O(N log(N)loglog(N*
  • 固定宽度数论变换:O(N log(N)在几十亿位数时比SSA快*

*请注意,这些示例阈值是近似的,并且可能变化很大——通常超过10倍。

因此,可能还有更多的问题,但您的第一个问题是使用指向数组的指针数组。由于您使用的数组大小是2的幂,因此与连续分配元素和使用整数除法将长数组中的数字折叠成行相比,这是一个特别大的性能打击。

不管怎样,这是我对一个问题的第一个猜测。正如我所说,可能还有更多,我会在发现它们时补充这个答案。

编辑:这可能只是问题的一小部分。这个问题很可能是Luchian Grigore所说的涉及2次幂的缓存线争用问题。

我验证了我的担忧对于天真算法是有效的。如果数组是连续的,那么朴素算法的时间几乎减少了50%。以下是pastebin上的代码(使用依赖于C++11的SquareMatrix类)。