为什么我的斯特拉森矩阵乘法很慢?
Why is my Strassen's Matrix Multiplication slow?
我用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类)。
- C++:根据斯特劳斯特鲁普的例子播种随机数
- 输入 A,B 输出 [A,B] 中的所有阿姆斯特朗数 1 和 1<=A<=B<=10^7
- 如何在特拉维斯CI中使用最新的提升版本?
- 如何在特拉维斯 CI 上安装 C++17 的 clang?
- 多米诺骨牌计划。我不知道如何将我的矢量拉到空白中以打印出来
- 如何修复我的 c++ 毕达哥拉斯三重查找器中的'access violation reading location'错误?
- 从字符到康斯特* 字符的转换无效,代码有什么问题?
- 用于双向迭代器的高德纳-莫里斯-普拉特算法
- 为什么我的埃拉托色尼代码筛子无限循环.我已经用几个数字进行了测试
- C++ 斯特劳斯特鲁普的"std_lib_facilities.h"字符串结构 – 警告:无符号表达式的比较
- 程序中的错误,无法在用户给定的间隔之间查找阿姆斯特朗数
- 为什么我的斯特拉森矩阵乘法很慢?
- 第4章 斯特劳斯特鲁普钻头一个具有挑战性的一步(至少对我来说!
- 平行的戴克斯特拉
- 如何做真正的斯特图尔?不会引入实际字符串
- 我相信斯特劳斯图普的书,第三版第368页有一个错别字。有人可以确认吗?
- 为什么达尔文的斯特托德线不安全?
- 有没有安全版本的斯特伦
- 带有二维数组的戴克斯特拉算法
- 我必须训练维奥拉-琼斯算法与我的数据库,以获得准确的结果