【问题标题】:Why is Strassen matrix multiplication so much slower than standard matrix multiplication?为什么 Strassen 矩阵乘法比标准矩阵乘法慢得多?
【发布时间】:2012-07-14 19:03:15
【问题描述】:

我用 C++、Python 和 Java 编写了矩阵乘法程序,并测试了它们对两个 2000 x 2000 矩阵相乘的速度(请参阅post)。标准的 ikj 实现 - 在 中 - 采用:

现在我已经在 Python 和 C++ 中实现了 Strassen algorithm for matrix multiplication - 在 中 - 就像在维基百科上一样。这些是我的时间:

  • C++:45 分钟 (Source)
  • Python:10 小时后被杀死 (Source)

为什么 Strassen 矩阵乘法比标准矩阵乘法慢很多?


想法:
  • 一些缓存效果
  • 实施:
    • 错误(生成的 2000 x 2000 矩阵是正确的)
    • null 乘法(对于 2000 x 2000 -> 2048 x 2048 应该没那么重要)

这尤其令人惊讶,因为它似乎与其他人的经历相矛盾:


编辑:在我的情况下,Strassen 矩阵乘法较慢的原因是:

  • 我让它完全递归(参见 tam)
  • 我有两个函数strassenstrassenRecursive。如果需要,第一个将矩阵的大小调整为 2 的幂,并调用第二个。但是strassenRecursive 并没有递归调用自己,而是strassen

【问题讨论】:

  • 没查过,不过分配了很多新向量。我想内存分配时间是杀死它的原因。
  • Voo 的回答基本上也涵盖了内存分配问题,因为尽早停止递归会减少分配次数。顺便说一句:在我的计算机上,我发现截止值约为 250 是一个不错的值。
  • 顺便说一句,您发布的来源不能被任何人试验,因为您不发布数据文件。这意味着除了推测之外没有人可以做任何事情。
  • @DeadMG:实际上数据文件在那里,只是在测试目录中的几个级别。

标签: c++ performance matrix multiplication strassen


【解决方案1】:

基本问题是您使用 strassen 实现递归到叶大小为 1。 Strassen 的算法具有更好的 Big O 复杂度,但常数确实在现实中很重要,这意味着实际上你最好使用标准的 n^3 矩阵乘法来解决更小的问题。

所以要大大改进你的程序而不是这样做:

if (tam == 1) {
        C[0][0] = A[0][0] * B[0][0];
        return;
    }

使用if (tam == LEAF_SIZE) // iterative solution hereLEAF_SIZE 应该是一个常数,您必须为给定的架构通过实验确定。根据架构的不同,它可能更大或更小 - 有些架构的 strassen 常数因子非常大,以至于对于合理的矩阵大小,它基本上总是比简单的 n^3 实现更差。这一切都取决于。

【讨论】:

  • @Mysticial 啊,这样更好,你的时间最好花在回答人类难以回答的问题上 ;)
  • 你是对的。我在这个脚本中添加了LEAF_SIZEgithub.com/MartinThoma/matrix-multiplication/blob/master/…。对于叶子大小10,时间下降到 66.50 秒,20 下降到 29.96 秒,50 下降到 18.80 秒。与更改代码中的值、重新编译、测试和尝试其他值相比,我怎样才能更好地(更结构化、更自动地)测试 LEAF_SIZE 的良好值?您知道绘制它的简单可能性吗? (我是否应该再问一个问题,因为这似乎与我之前的问题不同?)
  • @moose 好吧,让程序将叶子大小作为输入参数。然后我个人执行以下操作:对于每个叶子大小,运行程序十次(越多越好,但 10 有点准确)并将所有值存储在文本文件中(64.txt, 128.txtetc)——这显然是一个 shell 脚本工作。然后使用一个简单的脚本(我喜欢 python),它需要运行时,丢弃 2 个最快/最慢的并计算其余部分的平均值并将该数据输出为 CSV。 CSV 有一个很大的优势,即 excel/openoffice 和 co 都可以读取它并通过两次点击生成漂亮的图表。
  • 哦,请注意,任何不是 2 的幂的叶子大小都将等于 2 的下一个较低的幂(如果我现在不完全混淆的话),所以没有无论如何要测试这么多。
  • 感谢您的帮助。我刚刚绘制了结果:cloud.github.com/downloads/MartinThoma/matrix-multiplication/…
【解决方案2】:

嗯,“算术运算”并不是唯一重要的事情。这不像其他所有东西都是免费的。

我的天真猜测是,所有这些内存分配和复制都比减少算术运算所带来的收益...

尤其是内存访问,当它从缓存中取出时可能会非常昂贵,相比之下,算术运算可以被认为是免费的:-)

【讨论】:

  • ..并且,在 C++ 的情况下,优化可能是在预先分配的足够大的内存块上使用 placement new
  • 同意,我认为这里发生的所有内存机制都是减速的重要原因。
【解决方案3】:

虽然 Strassen 算法具有较小的大 O 表示法,但为了利用这一点,您需要乘以在大多数标准机器甚至超级计算机上都太大而无法求解的矩阵。

这样想

一个问题是 x^3 ,另一个是 X^1.6734 + 8x^(1/2) +x .....

【讨论】:

  • 并非如此。您通常会在 Strassen 的现代机器上获得数百个临界值。确实,在当今时代,600x600 矩阵是。 50k x 50k 矩阵的问题在今天并不值得注意(9gb 内存?有 16gb+ 的桌面)
  • 您可能指的是 Coppersmith-Winograd 算法:en.wikipedia.org/wiki/Coppersmith%E2%80%93Winograd_algorithm
【解决方案4】:

我记得我在大学时也做过同样的事情。 我的实现是用 Java 实现的。我还写了一个脚本来测试代码,我有超过 10000 个测试用例,包含不同大小的随机矩阵 (22) ~ (81928192)。我没有让递归进入标量级别,我使用 2 的所有幂作为停止点。 我发现了一个 Strassen 算法更有效的范围,以及一个比朴素算法更差的范围。

我没有调查缓存、内存或 JVM(垃圾收集)。

当我在全班同学面前展示时,我将这些发现归因于 Strassen 算法的渐近复数是根据乘法次数来衡量的。它是在计算机做加法比乘法快的时代设计的。

如今,CPU 的倍增速度与它们添加的速度一样快(周期数)。 如果检查这两种算法,您会发现只有在大小小于 2^10 时(如果我没记错的话),Strassen 的算术运算比朴素算法少。

【讨论】:

    猜你喜欢
    • 2012-11-13
    • 1970-01-01
    • 2010-12-27
    • 2015-07-19
    • 1970-01-01
    • 2012-06-22
    • 1970-01-01
    • 2018-11-29
    • 1970-01-01
    相关资源
    最近更新 更多