【问题标题】:speed up Matrix Multiplication by SSE2通过 SSE2 加速矩阵乘法
【发布时间】:2014-07-24 19:01:56
【问题描述】:

我想知道 SSE2 如何加快矩阵乘法

这是我的代码

int mat_mult_simd(double *a, double *b, double *c, int n)
{
   __m128d c1,c2,a1,a2,b1;

   for(int i=0; i<n/2; i++){
      for(int j=0; j<n/2; j++){
          c1 = _mm_load_pd(c+(2*j*n)+(i+2));
          c2 = _mm_load_pd(c+n+(2*j*n)+(i+2));
          for(int k=0; k<n; k++){
             a1 = _mm_load1_pd(a+k+(2*j*n));
             a2 = _mm load1_pd(a+n+k+(2*j*n));
             b1 = _mm_load_pd(b+(k*n)+(i*2));
             c1 = _mm_add_pd(c1, _mm_mul_pd(a1,b1));
             c2 = _mm_add_pd(c2, _mm_mul_pd(a2,b1));
          }
          __mm_store_pd(c+(2*j*n)+(i+2), c1);
          __mm_store_pd(c+n+(2*j*n)+(i+2), c2);
      }
   }
   return 0;
}

每个参数的含义

'a' = 向量 a(MAT_SIZE*MAT_SIZE)

'b' = 向量 b(MAT_SIZE*MAT_SIZE)

'c' = 向量 c(MAT_SIZE*MAT_SIZE)

'n' = MAT_SIZE 是常数(它总是偶数且 >=2)

这段代码加速了大约 X4。反对

int mat_mult_default(double *a, double *b, double *c, int n)
{
 double t;
 for(int i=0; i<n; i++){
    for(int j=0; j<n; j++){
    t=0.0;
    for(int k=0; k<n; k++)
       t += a[i*n+k] * b[k*n+j];
    c[i*n+j] = t;
    }
 }
}

但我想加快速度。我通常试验 MAT_SIZE 1000*1000 或 2000*2000。 我怎样才能加快速度?还有其他方法可以索引吗?我真的很想知道。谢谢。

【问题讨论】:

  • -1 只要求更好的性能而不给出任何理由是不现实的。说代码比某些东西快 4 倍而不指定它是什么也是不合理的。你需要做更多的努力来提出一个准确而具体的问题。您需要提供完整的基准测试程序,以明确您的数据来自何处。您需要提供典型的矩阵大小、样本数据等。
  • -1 为什么要自己编写这样的基本例程?使用英特尔 MKL 之类的库。如果是为了你学习SSE,请加这个。
  • @user2799037 写低级代码有什么问题?例如,每个像样的视频编码器/解码器都使用自己的 SIMD 程序集
  • @z̫͋ 低级代码只有在您知道自己在做什么时才有用。如果已经有高度优化的例程可用,为什么不使用它们呢?
  • 您似乎是 SO 新手。让我给你一些建议。搜索 SSE 标签并单击投票以按最高投票排序。通读任务和答案。例如,您可以通过添加矩阵乘法来缩小搜索范围。这样做可以学到很多东西(比从许多书籍中学到的更多),并且可能会回答您自己的问题。

标签: c matrix simd intrinsics sse2


【解决方案1】:

你可以做一些事情。显而易见的是将工作分成几个线程(每个核心 1 个)。您可以使用 OpenMP(最简单)、Intel TBB 或其他多线程库。 这将在多核机器上提供显着的改进。

另一件事是查看反汇编(通过您最喜欢的调试器) - 查看编译器如何处理您用于索引的所有乘法,其中一些可以被消除。

您的代码在一个循环中执行 2 次计算,尝试执行更多 4 或 8 次以获得更好的局部性。例如。 a1 和 a2 可以与它​​们已经在 L1 缓存中的邻居一起计算。实际上,您可以通过一次加载操作来加载它们。

确保各种数组都是 SSE 对齐的(16 字节),并更改您的代码以使用对齐的读/写。

我会把多线程留到最后,因为发现错误更难。

【讨论】:

    【解决方案2】:

    只需使用正确的库,例如英特尔数学内核库或类似的高度优化的线性代数包(OpenBLAS、AMD 核心数学库、ATLAS 等)。与手写代码相比,它们被认为更快。他们有时甚至对指令集和缓存大小进行了特定于处理器的优化。他们是各自领域的专业人士。除非您打算发表一篇关于您自己的优化的论文,否则请选择图书馆。

    在德国计算机杂志的latest issue 中,他们声称编译器足够聪明,可以单独使用 SSE 或 AVX。只需编写正确的循环,自动矢量化器就会带来最好的结果。这适用于最新的英特尔编译器。微软的编译器太垃圾了。在某些情况下,使用正确的编译器标志,英特尔的编译器甚至会检测到您编写了矩阵乘法并用正确的调用替换它。或者你必须检查文档,学习这样的包并不难。

    【讨论】:

    • 我认为这根本不是一个很好的答案。你能解释一下 MKL 会比问题中的代码做得更好吗?
    • 当然,当今大多数主流编译器都可以自动矢量化。但这并不意味着他们实际上做得很好。有经验的程序员自动矢量化和手动矢量化之间的差距仍然很大。部分原因是程序员具有编译器没有的特定领域知识。
    • 我还不知道如何使用 MKL。我想知道是否会有另一种索引方式。
    • @Mysticial 我提到的来源完全相反。它声称 MKL 击败了手写代码(抱歉,它只有德语)。如果你有特定领域的知识,你应该使用它来选择更好的解决方法,因为有一堆优化的特殊情况。所以这不是一个论点。
    • 除了“使用编译器或库”答案的“促销”组成部分之外,还有一个非常实用的观点:低级内在函数/汇编代码永远不会真正可移植 - 你需要一次又一次地重写它们 -对于每个新平台和每个新的“意外”工作负载/环境。当然,我不同意“做得不好”的说法:现在有足够的机制来“推动”和“调整”好的编译器或库,以便做得很好。当然,这些“编译器/lib 调整”也需要一些专业知识,但它们仍然便宜得多,而且通常 100% 可移植。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    • 1970-01-01
    • 2017-01-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多