用于使用AVX512生成掩模的BMI

Z b*_*son 5 x86 simd riscv avx512 bmi

我受到了这个链接的启发 https://www.sigarch.org/simd-instructions-considered-harmful/来研究AVX512的表现.我的想法是使用AVX512掩码操作可以删除循环后的清理循环.

这是我正在使用的代码

void daxpy2(int n, double a, const double x[], double y[]) {
  __m512d av = _mm512_set1_pd(a);
  int r = n&7, n2 = n - r;
  for(int i=-n2; i<0; i+=8) {
    __m512d yv = _mm512_loadu_pd(&y[i+n2]);
    __m512d xv = _mm512_loadu_pd(&x[i+n2]);
    yv = _mm512_fmadd_pd(av, xv, yv);
    _mm512_storeu_pd(&y[i+n2], yv);
  }
  __m512d yv = _mm512_loadu_pd(&y[n2]);
  __m512d xv = _mm512_loadu_pd(&x[n2]);
  yv = _mm512_fmadd_pd(av, xv, yv);
  __mmask8 mask = (1 << r) -1;
  //__mmask8 mask = _bextr_u32(-1, 0, r);
  _mm512_mask_storeu_pd(&y[n2], mask, yv);
}
Run Code Online (Sandbox Code Playgroud)

我认为使用BMI1和/或BMI2指令可以生成具有更少指令的掩码.然而,

__mmask8 mask = _bextr_u32(-1, 0, r)
Run Code Online (Sandbox Code Playgroud)

并不比(指令的数量)更好

__mmask8 mask = (1 << r) -1;
Run Code Online (Sandbox Code Playgroud)

请参阅https://godbolt.org/z/BFQCM3https://godbolt.org/z/tesmB_.

这似乎是由于_bextr_u32无论如何都换了8.

是否可以使用较少的指令(例如使用BMI或其他方法)或更优化的方式生成掩码?


我在AVX512的结果链接中增加了表格.

ISA                           | MIPS-32 | AVX2  | RV32V | AVX512 |
******************************|*********|****** |*******|******* |
Instructions(static)          |      22 |   29  |    13 |     28 |
Instructions per Main Loop    |       7 |    6* |    10 |      5*|
Bookkeeping Instructions      |      15 |   23  |     3 |     23 |
Results per Main Loop         |       2 |    4  |    64 |      8 |
Instructions (dynamic n=1000) |    3511 | 1517**|   163 |    645 |

*macro-op fusion will reduce the number of uops in the main loop by 1
** without the unnecessary cmp instructions it would only be 1250+ instructions.
Run Code Online (Sandbox Code Playgroud)

该链接的作者没有考虑宏观融合.对于AVX和AVX512,这将使主循环中的指令数减少1.即4个指令,而不是AVX512的5个指令.对于n = 1000,主循环的执行指令数应该是-n.

此外,我认为,如果该链接的作者们数从0最多0,而不是来自ncmp他们可以跳过的-O3 -xCOMMON-AVX512指令,因为我有(见下面的组装)在主回路所以AVX它chould主已经有5点指示循环(4与宏操作融合)总共1000+指令而不是1517.

这是ICC19和ICC19的组装 jl

daxpy2(int, double, double const*, double*):
    mov       eax, edi                                      #6.13
    and       eax, 7                                        #6.13
    movsxd    r9, edi                                       #6.25
    sub       r9, rax                                       #6.21
    mov       ecx, r9d                                      #7.14
    neg       ecx                                           #7.14
    movsxd    rcx, ecx                                      #7.14
    vbroadcastsd zmm16, xmm0                                #5.16
    lea       rdi, QWORD PTR [rsi+r9*8]                     #9.35
    lea       r8, QWORD PTR [rdx+r9*8]                      #8.35
    test      rcx, rcx                                      #7.20
    jge       ..B1.5        # Prob 36%                      #7.20
..B1.3:                         # Preds ..B1.1 ..B1.3
    vmovups   zmm17, ZMMWORD PTR [rdi+rcx*8]                #10.10
    vfmadd213pd zmm17, zmm16, ZMMWORD PTR [r8+rcx*8]        #10.10
    vmovups   ZMMWORD PTR [r8+rcx*8], zmm17                 #11.23
    add       rcx, 8                                        #7.23
    js        ..B1.3        # Prob 82%                      #7.20
..B1.5:                         # Preds ..B1.3 ..B1.1
    vmovups   zmm17, ZMMWORD PTR [rsi+r9*8]                 #15.8
    vfmadd213pd zmm16, zmm17, ZMMWORD PTR [rdx+r9*8]        #15.8
    mov       edx, -1                                       #17.19
    shl       eax, 8                                        #17.19
    bextr     eax, edx, eax                                 #17.19
    kmovw     k1, eax                                       #18.3
    vmovupd   ZMMWORD PTR [r8]{k1}, zmm16                   #18.3
    vzeroupper                                              #19.1
    ret                                                     #19.1
Run Code Online (Sandbox Code Playgroud)

哪里

    add       r8, 8
    js        ..B1.3
Run Code Online (Sandbox Code Playgroud)

应该宏观操作融合到一条指令.然而,正如彼得科德斯在这个答案中 所指出的那样,js无法融合.编译器可能已生成2*n/vec_size而不是已融合的编译器.

wim*_*wim 5

如果您使用以下 BMI2 内在函数,则可以保存一条指令:

  __mmask8 mask = _bzhi_u32(-1, r);
Run Code Online (Sandbox Code Playgroud)

而不是__mmask8 mask = (1 << r) -1;. 请参阅Godbolt 链接

bzhi指令从指定位置开始将高位清零。使用寄存器操作数时,bzhi延迟为 1 个周期,每个周期的吞吐量为 2。

  • 我会进一步建议使用 64 位计数器,并用 `i!=0` 替换循环条件。如果您知道 `n&gt;=8`,您实际上可以加载和存储一些重叠的内存而无需生成掩码:https://godbolt.org/z/UV_hYy (3认同)

cht*_*htz 5

除了@wim 使用_bzhi_u32, 而不是的回答之外_bextr_u32,您还应该:

  • 屏蔽最后的_mm512_loadu_pd指令,避免加载无效内存(/sf/answers/3817115781/),或对非有限值进行算术运算。
  • 到处使用 64 位整数(实际上是有符号或无符号)以避免 movsxd符号扩展。这在 64 位系统上通常是一个很好的建议,除非您需要存储大量索引变量。
  • 使用i!=0而不是i<0作为循环条件来获得一个jne而不是js,因为这更好地与add指令配对: https //stackoverflow.com/a/31778403
  • 一些小事情,而不是n2=n-r,你也可以计算n2 = n & (-8)n2 = n ^ r。不确定,如果这会产生相关差异(icc 似乎不知道或不关心)。Godbolt-Link

void daxpy2(size_t n, double a, const double x[], double y[]) {
  __m512d av = _mm512_set1_pd(a);
  size_t r = n&7, n2 = n & (-8);
  for(size_t i=-n2; i!=0; i+=8) {
    __m512d yv = _mm512_loadu_pd(&y[i+n2]);
    __m512d xv = _mm512_loadu_pd(&x[i+n2]);
    yv = _mm512_fmadd_pd(av, xv, yv);
    _mm512_storeu_pd(&y[i+n2], yv);
  }
  __mmask8 mask = _bzhi_u32(-1, r);
  __m512d yv = _mm512_mask_loadu_pd(_mm512_undefined_pd (), mask, &y[n2]);
  __m512d xv = _mm512_mask_loadu_pd(_mm512_undefined_pd (), mask, &x[n2]);
  yv = _mm512_mask_fmadd_pd(av, mask, xv, yv);
  _mm512_mask_storeu_pd(&y[n2], mask, yv);
}
Run Code Online (Sandbox Code Playgroud)

为了进一步减少指令的数量,您可以使用指针增量,例如,像这样 (但是这会增加循环内的指令)。


归档时间:

查看次数:

250 次

最近记录:

6 年,8 月 前