我用C ++编写了两个矩阵乘法程序:常规MM (source)和Strassen的MM (source),两者都在大小为2 ^ kx 2 ^ k的矩形矩阵上运算(换句话说,正方形均匀大小的矩阵)。
结果太可怕了。对于1024 x 1024矩阵,常规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
EDIT1。 拳头,很多很棒的建议。感谢您抽出宝贵时间和分享知识。
我实施了更改(保留了我的所有代码),添加了截止点。 具有截止512的2048x2048矩阵的MM已经给出了良好的结果。 常规MM:191.49s Strassen的MM:112.179s 很显着的提高。 结果是使用Visual Studio 2012在具有英特尔迅驰处理器的史前联想X61 TabletPC上获得的。 我会做更多检查(以确保我得到正确的结果),并将公布结果。
答案 0 :(得分:26)
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个元素。
这类似于使用合并或快速排序并一直递归到一个元素。为了提高效率,您需要在大小变小时停止递归并回退到经典算法。
在快速/合并排序中,您将回退到低开销O(n^2)
插入或选择排序。在这里,您将回到正常的O(n^3)
矩阵乘法。
您回退经典算法的阈值应该是可调阈值,可能会因硬件和编译器优化代码的能力而异。
对于类似Strassen乘法的东西,其优势仅比经典O(2.8074)
高O(n^3)
,如果此阈值非常高,请不要感到惊讶。 (成千上万的元素?)
在某些应用程序中,可能会有许多算法,每个算法的复杂性都会降低,但会增加Big-O。结果是多种算法在不同大小下变得最佳。
大整数乘法是一个臭名昭着的例子:
*请注意,这些示例阈值是近似值,可能会有很大差异 - 通常超过10倍。
答案 1 :(得分:3)
所以,这可能会有更多的问题,但你的第一个问题是你正在使用指向数组的指针数组。而且由于你使用的是2的幂的数组大小,这对于连续分配元素并使用整数除法将长数组折叠成行是一个特别大的性能影响。
无论如何,这是我对问题的第一次猜测。正如我所说,可能会有更多,我会在发现它们时添加这个答案。
编辑:这可能只会对问题产生少量影响。问题很可能是Luchian Grigore涉及cache line contention issues with powers of two。
我确认我的关注对于天真的算法是有效的。如果阵列是连续的,那么天真算法的时间会下降近50%。这是the code for this (using a SquareMatrix class that is C++11 dependent) on pastebin。