13

我用 C++ 编写了两个矩阵乘法程序:Regular MM (source)和 Strassen's MM (source),它们都在大小为 2^kx 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。拳头,很多很好的建议。感谢您抽出宝贵时间分享知识。

我实施了更改(保留了我的所有代码),添加了截止点。MM 的 2048x2048 矩阵,截止 512 已经给出了很好的结果。常规 MM:191.49s Strassen 的 MM:112.179s 显着改善。结果是在配备 Intel Centrino 处理器的史前联想 X61 平板电脑上使用 Visual Studio 2012 获得的。我会做更多检查(以确保我得到正确的结果),并将公布结果。

4

2 回答 2

27

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 倍。

于 2012-11-26T07:41:37.053 回答
3

因此,可能存在更多问题,但您的第一个问题是您正在使用指向数组的指针数组。而且由于您使用的是 2 的幂的数组大小,因此与连续分配元素和使用整数除法将长数字数组折叠成行相比,这对性能造成了特别大的影响。

无论如何,这是我对问题的第一个猜测。正如我所说,可能还有更多,当我发现它们时,我会添加到这个答案中。

编辑:这可能只会导致问题的一小部分。问题很可能是 Luchian Grigore所指的涉及具有 2 次幂的高速缓存行争用问题

我证实我的担忧对于朴素算法是有效的。如果数组是连续的,那么朴素算法的时间会减少近 50%。这是pastebin 上的代码(使用依赖于 C++11 的 SquareMatrix 类)

于 2012-11-26T07:12:27.737 回答