【问题标题】:Matrix multiplication: Strassen vs. Standard矩阵乘法:施特拉森与标准
【发布时间】:2011-05-17 07:37:48
【问题描述】:

我尝试使用 C++ 实现 Strassen algorithm 进行矩阵乘法,但结果并非如我所愿。如您所见,strassen 总是比标准实现花费更多的时间,并且只有 2 次方的维度与标准实现一样快。什么地方出了错?

matrix mult_strassen(matrix a, matrix b) {
if (a.dim() <= cut)
    return mult_std(a, b);

matrix a11 = get_part(0, 0, a);
matrix a12 = get_part(0, 1, a);
matrix a21 = get_part(1, 0, a);
matrix a22 = get_part(1, 1, a);

matrix b11 = get_part(0, 0, b);
matrix b12 = get_part(0, 1, b);
matrix b21 = get_part(1, 0, b);
matrix b22 = get_part(1, 1, b);

matrix m1 = mult_strassen(a11 + a22, b11 + b22); 
matrix m2 = mult_strassen(a21 + a22, b11);
matrix m3 = mult_strassen(a11, b12 - b22);
matrix m4 = mult_strassen(a22, b21 - b11);
matrix m5 = mult_strassen(a11 + a12, b22);
matrix m6 = mult_strassen(a21 - a11, b11 + b12);
matrix m7 = mult_strassen(a12 - a22, b21 + b22);

matrix c(a.dim(), false, true);
set_part(0, 0, &c, m1 + m4 - m5 + m7);
set_part(0, 1, &c, m3 + m5);
set_part(1, 0, &c, m2 + m4);
set_part(1, 1, &c, m1 - m2 + m3 + m6);

return c; 
}


计划
矩阵.h http://pastebin.com/TYFYCTY7
matrix.cpp http://pastebin.com/wYADLJ8Y
main.cpp http://pastebin.com/48BSqGJr

g++ main.cpp matrix.cpp -o matrix -O3.

【问题讨论】:

  • 如果您希望有人帮助您,您应该提供当前结果和预期结果(可能还有乘法函数)。放整个代码太多了。另一件事,如果问题是关于作业的,请添加作业标志
  • 这不是家庭作业。我只是对实现 strassen 算法感兴趣,因为它应该更快。标准的复杂度为 O(n^3),而斯特拉森的复杂度为 O(n^2.8),因为它需要的乘法少。

标签: c++ performance matrix matrix-multiplication strassen


【解决方案1】:

一些想法:

  • 您是否对其进行了优化,以考虑使用 0 填充两个大小的非幂矩阵?我认为该算法假设您不必费心将这些术语相乘。这就是为什么你会得到运行时间在 2^n 和 2^(n+1)-1 之间恒定的平坦区域的原因。通过不将您知道为零的项相乘,您应该能够改善这些领域。或者也许 Strassen 只适用于 2^n 大小的矩阵。
  • 考虑到“大”矩阵是任意的,并且算法仅比简单情况 O(N^3) 与 O(N^2.8) 略好。在尝试更大的矩阵之前,您可能看不到可衡量的收益。例如,我做了一些有限元建模,其中 10,000x10,000 矩阵被认为是“小”。从您的图表中很难看出,但看起来 511 案例在 Stassen 案例中可能更快。
  • 尝试使用各种优化级别进行测试,包括完全不进行优化。
  • 这个算法似乎假设乘法比加法更昂贵。这在 40 年前首次开发时确实如此,但我相信在更现代的处理器中,加法和乘法之间的差异变得更小了。这可能会降低似乎减少乘法但增加加法的算法的有效性。
  • 您是否查看过其他一些 Strassen 实现以寻找想法?尝试对一个已知良好的实现进行基准测试,看看你能获得多快。

【讨论】:

  • 在使用 Strassen 算法(使用 Z 顺序)时修改矩阵的存储顺序也可能有助于加快速度,使内存访问更加缓存友好。
【解决方案2】:

好吧,我不是这个领域的专家,但除了处理速度之外,这里可能还有其他问题。首先,strassen 方法使用更多的堆栈并且有更多的函数调用,这增加了内存移动。你的堆栈越大,你就会受到一定的惩罚,因为它需要从操作系统请求更大的帧。加上你使用动态分配,这也是一个问题。

尝试使用固定大小(带模板参数)的矩阵类?这至少可以解决分配问题。

注意:我不确定 it 事件能否与您的代码一起正常工作。您的矩阵类使用指针,但没有复制构造函数或赋值运算符。最后你也会泄漏内存,因为你没有析构函数......

【讨论】:

    【解决方案3】:

    Strassen 的大 O 是 O(N ^ log 7) 与 O(N ^ 3) 常规相比,即 log 7 base 2 略小于 3。

    这是你需要做的乘法次数。

    它假设您拥有的任何其他东西都没有成本,并且只有当 N 变得足够大而您的可能没有时,它也应该“更快”。

    您的大部分实现都在创建大量子矩阵,我猜您存储它们的方式是每次执行此操作时都必须分配内存和复制。如果可以的话,拥有某种“切片”矩阵和逻辑转置矩阵将帮助您优化可能是过程中最慢的部分。

    【讨论】:

      【解决方案4】:

      我对我的 Stassen 乘法运算速度之快感到震惊 实现是:

      http://ezekiel.vancouver.wsu.edu/~cs330/lectures/linear_algebra/mm/mm.c

      当 n=1024 时,我的机器上的速度几乎提高了 16 倍。 我能解释这么多加速的唯一方法是 我的算法对缓存更友好——即,它专注于小 矩阵的片段,因此数据更加本地化。

      您的 C++ 实现中的开销可能太高了—— 编译器生成的临时变量比真正需要的要多。 我的实现试图通过重用内存来最小化这种情况 可能。

      【讨论】:

      • 对于 1024 x 1024 矩阵,您需要一个阻塞算法 - 幼稚的实现会非常慢,因为每次乘法平均会出现一次缓存未命中。例如,如果您采用 n = 1023 或 1025,您确实需要研究时间如何变化,并可能相应地更改矩阵的布局。
      【解决方案5】:

      远射,但您是否考虑过标准乘法可能会被编译器优化?你能关掉优化吗?

      【讨论】:

      • 关闭优化会增加时间,但会给出相同的图表。
      • 嗯。我想我不能再帮你了。似乎有很多函数调用在您的 Strassen 实现中访问主内存。也许这就是瓶颈?天真的乘法会大量使用寄存器。我建议使用 BLAS 库进行矩阵乘法。
      • 关闭优化会扼杀任何基准测试的意义,而这些基准测试有望在现实生活中提供有用的结果。
      • 哇。否定代表有什么用?我没有看到你提出任何建议。
      猜你喜欢
      • 1970-01-01
      • 2014-05-13
      • 1970-01-01
      • 2012-07-14
      • 1970-01-01
      • 1970-01-01
      • 2012-11-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多