Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Performance 密集矩阵向量乘法最有效的实现方式是什么?_Performance_Matrix_Language Agnostic_Linear Algebra_Matrix Multiplication - Fatal编程技术网

Performance 密集矩阵向量乘法最有效的实现方式是什么?

Performance 密集矩阵向量乘法最有效的实现方式是什么?,performance,matrix,language-agnostic,linear-algebra,matrix-multiplication,Performance,Matrix,Language Agnostic,Linear Algebra,Matrix Multiplication,如果M是稠密的M x n矩阵,v是n分量向量,那么乘积u=Mv是由u[i]=sum[i,j]*v[j]给出的M分量向量,1可能使用已建立的BLAS实现是最常见的。除此之外,简单实现还存在一些值得关注的问题。例如,在C或C++中,指针混叠常常会妨碍很多优化,因此 这看起来真的很痛,执行起来更痛。编译器必须这样做,因为u可能使用M和/或v进行别名,因此对u中的存储进行了极大的怀疑,因为编译器可以插入一个别名测试,并有一个快速的路径来处理尼斯案例。在Fortran中,过程参数在默认情况下不能使用别名,

如果M是稠密的M x n矩阵,v是n分量向量,那么乘积u=Mv是由u[i]=sum[i,j]*v[j]给出的M分量向量,1可能使用已建立的BLAS实现是最常见的。除此之外,简单实现还存在一些值得关注的问题。例如,在C或C++中,指针混叠常常会妨碍很多优化,因此

这看起来真的很痛,执行起来更痛。编译器必须这样做,因为u可能使用M和/或v进行别名,因此对u中的存储进行了极大的怀疑,因为编译器可以插入一个别名测试,并有一个快速的路径来处理尼斯案例。在Fortran中,过程参数在默认情况下不能使用别名,因此不存在此问题。这是一个典型的原因,为什么在Fortran中随机键入的代码比在C中更快-我的回答的其余部分不会涉及这一点,我只是想让C代码更快一点,最后我回到一个主要的m列。在C中,别名问题可以是限制,但它唯一能做的是,使用显式累加器而不是求和到u[i]中不是侵入性的,它也能做到这一点,但不依赖于神奇的关键字

void matvec(double *M, size_t n, size_t m, double *v, double * restrict u)
{
    for (size_t i = 0; i < m; i++) {
      for (size_t j = 0; j < n; j++) {
        u[i] += M[i * n + j] * v[j];
      }
    }
}
它不再是标量,所以这很好。但并不理想。虽然这里有8个FMA,但它们被安排在四对相关FMA中。纵观整个循环,实际上只有4个独立的FMA依赖链。FMA通常具有较长的延迟和可观的吞吐量,但例如在Skylake上,它的延迟为4,吞吐量为2/周期,因此需要8个独立的FMA链来利用所有这些计算吞吐量。Haswell更糟糕的是,FMA的延迟为5,吞吐量为2/周期,因此它需要10条独立的链。另一个问题是,很难为所有这些FMA提供数据,上面的结构甚至没有真正尝试:它使用每个FMA 2个负载,而负载实际上与FMA具有相同的吞吐量,因此它们的比率应为1:1

可以通过展开外部循环来提高load:FMA比率,这让我们可以将v中的负载重新用于M的几行。这甚至不是缓存方面的考虑,但也有助于实现这一点。展开外部循环也可以实现拥有更多独立FMA链的目标。编译器通常不喜欢展开内部循环以外的任何内容,因此这需要一些时间。尾部迭代省略或:假设m是4的倍数

void matvec(double *M, size_t n, size_t m, double *v, double * restrict u)
{
    size_t i;
    for (i = 0; i + 3 < m; i += 4) {
      for (size_t j = 0; j < n; j++) {
        size_t it = i;
        u[it] += M[it * n + j] * v[j];
        it++;
        u[it] += M[it * n + j] * v[j];
        it++;
        u[it] += M[it * n + j] * v[j];
        it++;
        u[it] += M[it * n + j] * v[j];
      }
    }
}
如果我们不再懒惰,并最终采取一些措施,这个问题就会消失:

这很不错。负载:FMA比率为10:8,Haswell的蓄能器太少,因此仍有可能进行一些改进。其他一些有趣的展开组合是外部x内部4x3 12蓄能器、3个临时、5/4负载:FMA、5x2 10、2、6/5、7x2 14、2、8/7、15x15、1、16/15。这使它看起来通过展开外部循环更好,但是有太多不同的流,即使在流负载的意义上不流,也不利于自动预取,并且在实际流时,超过填充缓冲区的数量可能不好,实际细节很少。手动预取也是一种选择。获得一个真正好的MVM程序需要做更多的工作,尝试很多这些东西

将存储保存到u中以供内部循环之外使用意味着不再需要限制。我认为,最令人印象深刻的是,不需要SIMD内部函数就可以做到这一点——如果没有可怕的潜在别名的话,叮当很好地解决了这个问题。GCC和ICC确实尝试过,但展开不够,更多的手动展开可能会奏效

循环平铺也是一个选项,但这是MVM。平铺对于MMM来说是非常必要的,但是MMM有几乎无限量的数据重用,而MVM没有。只有向量被重用,矩阵只需流一次。与不适合缓存的向量相比,流式处理巨大矩阵的内存吞吐量可能是一个更大的问题

对于一个列major M,它是不同的,没有明显的循环携带依赖性。内存之间存在依赖关系,但它有很多时间。虽然load:FMA比率仍然必须降低,所以它仍然需要一些外部循环的展开,但总体来说似乎更容易处理。它可以重新安排以使用大部分添加,但FMA在HSW上具有高吞吐量,高于添加!。它不需要水平和,这很烦人,但它们发生在内部循环之外。作为回报,在内部循环中有存储。如果不尝试,我并不期望这些方法之间有很大的内在差异,似乎这两种方法都应该可以调整到计算吞吐量的80%到90%之间,以实现可缓存大小。讨厌的额外费用
负载本质上防止任意接近100%。

在线性代数操作中,缓冲区加载和卸载是瓶颈。这些算法已经饱和,因此您不会与任何方法有太大的性能差异。这不是您可以轻松实现和期望性能的东西。几十年的优化已经到位。为此,您可以安全地选择专门针对此类操作优化的MKL或OpenBLAS实现。流式加载movntdqa在写回内存上没有任何特殊功能,仅在USWC上执行,例如从视频内存复制。但是NT预取在WB内存上仍然是特殊的。Off-topic:由OP编辑以删除中断的尝试。你删除的答案现在可以回答这个问题了。因为我不能对删除的答案发表评论,所以我在这里给你打了一个最近的另一个答案。
void matvec(double *M, size_t n, size_t m, double *v, double * u)
{
    for (size_t i = 0; i < m; i++) {
      for (size_t j = 0; j < n; j++) {
        u[i] += M[i * n + j] * v[j];
      }
    }
}
.LBB0_4: # Parent Loop BB0_3 Depth=1
  vmovsd xmm1, qword ptr [rcx + 8*rax] # xmm1 = mem[0],zero
  vfmadd132sd xmm1, xmm0, qword ptr [r13 + 8*rax - 24]
  vmovsd qword ptr [r8 + 8*rbx], xmm1
  vmovsd xmm0, qword ptr [rcx + 8*rax + 8] # xmm0 = mem[0],zero
  vfmadd132sd xmm0, xmm1, qword ptr [r13 + 8*rax - 16]
  vmovsd qword ptr [r8 + 8*rbx], xmm0
  vmovsd xmm1, qword ptr [rcx + 8*rax + 16] # xmm1 = mem[0],zero
  vfmadd132sd xmm1, xmm0, qword ptr [r13 + 8*rax - 8]
  vmovsd qword ptr [r8 + 8*rbx], xmm1
  vmovsd xmm0, qword ptr [rcx + 8*rax + 24] # xmm0 = mem[0],zero
  vfmadd132sd xmm0, xmm1, qword ptr [r13 + 8*rax]
  vmovsd qword ptr [r8 + 8*rbx], xmm0
  add rax, 4
  cmp r11, rax
  jne .LBB0_4
void matvec(double *M, size_t n, size_t m, double *v, double * restrict u)
{
    for (size_t i = 0; i < m; i++) {
      for (size_t j = 0; j < n; j++) {
        u[i] += M[i * n + j] * v[j];
      }
    }
}
.LBB0_8: # Parent Loop BB0_3 Depth=1
  vmovupd ymm5, ymmword ptr [rcx + 8*rbx]
  vmovupd ymm6, ymmword ptr [rcx + 8*rbx + 32]
  vmovupd ymm7, ymmword ptr [rcx + 8*rbx + 64]
  vmovupd ymm8, ymmword ptr [rcx + 8*rbx + 96]
  vfmadd132pd ymm5, ymm1, ymmword ptr [rax + 8*rbx - 224]
  vfmadd132pd ymm6, ymm2, ymmword ptr [rax + 8*rbx - 192]
  vfmadd132pd ymm7, ymm3, ymmword ptr [rax + 8*rbx - 160]
  vfmadd132pd ymm8, ymm4, ymmword ptr [rax + 8*rbx - 128]
  vmovupd ymm1, ymmword ptr [rcx + 8*rbx + 128]
  vmovupd ymm2, ymmword ptr [rcx + 8*rbx + 160]
  vmovupd ymm3, ymmword ptr [rcx + 8*rbx + 192]
  vmovupd ymm4, ymmword ptr [rcx + 8*rbx + 224]
  vfmadd132pd ymm1, ymm5, ymmword ptr [rax + 8*rbx - 96]
  vfmadd132pd ymm2, ymm6, ymmword ptr [rax + 8*rbx - 64]
  vfmadd132pd ymm3, ymm7, ymmword ptr [rax + 8*rbx - 32]
  vfmadd132pd ymm4, ymm8, ymmword ptr [rax + 8*rbx]
  add rbx, 32
  add rbp, 2
  jne .LBB0_8
void matvec(double *M, size_t n, size_t m, double *v, double * restrict u)
{
    size_t i;
    for (i = 0; i + 3 < m; i += 4) {
      for (size_t j = 0; j < n; j++) {
        size_t it = i;
        u[it] += M[it * n + j] * v[j];
        it++;
        u[it] += M[it * n + j] * v[j];
        it++;
        u[it] += M[it * n + j] * v[j];
        it++;
        u[it] += M[it * n + j] * v[j];
      }
    }
}
.LBB0_8: # Parent Loop BB0_3 Depth=1
  vmovupd ymm5, ymmword ptr [rcx + 8*rdx]
  vmovupd ymm6, ymmword ptr [rcx + 8*rdx + 32]
  vfmadd231pd ymm4, ymm5, ymmword ptr [r12 + 8*rdx - 32]
  vfmadd231pd ymm3, ymm5, ymmword ptr [r13 + 8*rdx - 32]
  vfmadd231pd ymm2, ymm5, ymmword ptr [rax + 8*rdx - 32]
  vfmadd231pd ymm1, ymm5, ymmword ptr [rbx + 8*rdx - 32]
  vfmadd231pd ymm4, ymm6, ymmword ptr [r12 + 8*rdx]
  vfmadd231pd ymm3, ymm6, ymmword ptr [r13 + 8*rdx]
  vfmadd231pd ymm2, ymm6, ymmword ptr [rax + 8*rdx]
  vfmadd231pd ymm1, ymm6, ymmword ptr [rbx + 8*rdx]
  add rdx, 8
  add rdi, 2
  jne .LBB0_8
void matvec(double *M, size_t n, size_t m, double *v, double *u)
{
    size_t i;
    for (i = 0; i + 3 < m; i += 4) {
      double t0 = 0, t1 = 0, t2 = 0, t3 = 0;
      for (size_t j = 0; j < n; j++) {
        size_t it = i;
        t0 += M[it * n + j] * v[j];
        it++;
        t1 += M[it * n + j] * v[j];
        it++;
        t2 += M[it * n + j] * v[j];
        it++;
        t3 += M[it * n + j] * v[j];
      }
      u[i] += t0;
      u[i + 1] += t1;
      u[i + 2] += t2;
      u[i + 3] += t3;
    }
}
.LBB0_6: # Parent Loop BB0_3 Depth=1
  vmovupd ymm8, ymmword ptr [r10 - 32]
  vmovupd ymm9, ymmword ptr [r10]
  vfmadd231pd ymm6, ymm8, ymmword ptr [rdi]
  vfmadd231pd ymm7, ymm9, ymmword ptr [rdi + 32]
  lea rax, [rdi + r14]
  vfmadd231pd ymm4, ymm8, ymmword ptr [rdi + 8*rsi]
  vfmadd231pd ymm5, ymm9, ymmword ptr [rdi + 8*rsi + 32]
  vfmadd231pd ymm1, ymm8, ymmword ptr [rax + 8*rsi]
  vfmadd231pd ymm3, ymm9, ymmword ptr [rax + 8*rsi + 32]
  lea rax, [rax + r14]
  vfmadd231pd ymm0, ymm8, ymmword ptr [rax + 8*rsi]
  vfmadd231pd ymm2, ymm9, ymmword ptr [rax + 8*rsi + 32]
  add rdi, 64
  add r10, 64
  add rbp, -8
  jne .LBB0_6