首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用AVX512制作掩模的BMI

用AVX512制作掩模的BMI
EN

Stack Overflow用户
提问于 2019-02-21 14:15:54
回答 2查看 635关注 0票数 6

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

这是我正在使用的代码

代码语言:javascript
复制
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);
}

我认为使用BMI1和/或BMI2指令可以用较少的指令生成掩码。然而,

代码语言:javascript
复制
__mmask8 mask = _bextr_u32(-1, 0, r)

没有比(指令数量)更好的了

代码语言:javascript
复制
__mmask8 mask = (1 << r) -1;

https://godbolt.org/z/BFQCM3_

这似乎是因为_bextr_u32做了8的调整。

可以用更少的指令(例如,BMI或其他方法)或更优化的方法生成掩码吗?

我已经用AVX512的结果扩展了链接中的表。

代码语言:javascript
复制
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.

我认为,如果链接的作者从-n0,而不是从0n,他们可以跳过主循环中的cmp指令(见下面的程序集),所以对于AVX,主循环中应该有5个指令。

下面是带有ICC19和-O3 -xCOMMON-AVX512的程序集

代码语言:javascript
复制
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

哪里

代码语言:javascript
复制
    add       r8, 8
    js        ..B1.3

宏操作应该融合到一条指令上。然而,正如Peter 在这个答案中 js所指出的那样,js不能融合。编译器本来可以生成jl,但这可能是融合的。

我使用了Agner的试验实用程序来获取核心时钟(而不是参考时钟)、指令、uops退役。我这样做是为了SSE2 (实际上是带FMA的AVX2,但有128位向量),AVX2和AVX512用于三种不同的循环。

代码语言:javascript
复制
v1 = for(int64_t i=0;   i<n;  i+=vec_size) // generates cmp instruction
v2 = for(int64_t i=-n2; i<0;  i+=vec_size) // no cmp but uses js
v3 = for(int64_t i=-n2; i!=0; i+=vec_size) // no cmp and uses jne

vec_size = 2 for SSE, 4 for AVX2, and 8 for AVX512

vec_size version   core cycle    instructions   uops
2        v1        895           3014           3524
2        v2        900           2518           3535
2        v3        870           2518           3035
4        v1        527           1513           1777
4        v2        520           1270           1777
4        v3        517           1270           1541
8        v1        285            765            910
8        v2        285            645            910
8        v3        285            645            790

注意,核心时钟实际上不是循环版本的函数。它只依赖于循环的迭代。它与2*n/vec_size成正比。

代码语言:javascript
复制
SSE     2*1000/2=1000
AVX2    2*1000/4=500
AVX512  2*1000/8=250

指令的数量确实从v1更改为v2,但在v2和v3之间没有变化。对于v1,它与6*n/vec_size成正比,对于v2和v3 5*n/vec_size则成正比。

最后,对于v1和v2,uop的数量或多或少是相同的,而对于v3则是下降的。对于v1和v2,它与7*n/vec_size和v3 6*n/vec_size成正比。

下面是IACA3 for vec_size=2的结果

代码语言:javascript
复制
Throughput Analysis Report
--------------------------
Block Throughput: 1.49 Cycles       Throughput Bottleneck: FrontEnd
Loop Count:  50
Port Binding In Cycles Per Iteration:
--------------------------------------------------------------------------------------------------
|  Port  |   0   -  DV   |   1   |   2   -  D    |   3   -  D    |   4   |   5   |   6   |   7   |
--------------------------------------------------------------------------------------------------
| Cycles |  0.5     0.0  |  0.5  |  1.5     1.0  |  1.5     1.0  |  1.0  |  0.0  |  0.0  |  0.0  |
--------------------------------------------------------------------------------------------------

DV - Divider pipe (on port 0)
D - Data fetch pipe (on ports 2 and 3)
F - Macro Fusion with the previous instruction occurred
* - instruction micro-ops not bound to a port
^ - Micro Fusion occurred
# - ESP Tracking sync uop was issued
@ - SSE instruction followed an AVX256/AVX512 instruction, dozens of cycles penalty is expected
X - instruction not supported, was not accounted in Analysis

| Num Of   |                    Ports pressure in cycles                         |      |
|  Uops    |  0  - DV    |  1   |  2  -  D    |  3  -  D    |  4   |  5   |  6   |  7   |
-----------------------------------------------------------------------------------------
|   1      |             |      | 0.5     0.5 | 0.5     0.5 |      |      |      |      | vmovupd xmm1, xmmword ptr [r8+rax*8]
|   2      | 0.5         | 0.5  | 0.5     0.5 | 0.5     0.5 |      |      |      |      | vfmadd213pd xmm1, xmm2, xmmword ptr [rcx+rax*8]
|   2      |             |      | 0.5         | 0.5         | 1.0  |      |      |      | vmovups xmmword ptr [rcx+rax*8], xmm1
|   1*     |             |      |             |             |      |      |      |      | add rax, 0x2
|   0*F    |             |      |             |             |      |      |      |      | js 0xffffffffffffffe3
Total Num Of Uops: 6

IACA声称js宏融合了与Agner不一致的addtestp实用工具的性能计数器。见上文,v2与7*n/vec_size成正比,v3与6*n/vec_size成正比,这意味着js并不是宏观导火索。

我认为,除了指令的数量之外,链接的作者还应该考虑核心周期,或者是uop。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-02-21 21:57:53

如果使用以下BMI2内在特性,则可以保存一条指令:

代码语言:javascript
复制
  __mmask8 mask = _bzhi_u32(-1, r);

而不是__mmask8 mask = (1 << r) -1;。见螺栓连接

说明对从指定位置开始的高位进行零。对于寄存器操作数,bzhi的延迟为1周期,吞吐量为每周期2次。

票数 5
EN

Stack Overflow用户

发布于 2019-02-22 19:52:17

此外,对于@wim使用_bzhi_u32而不是_bextr_u32的回答,您应该:

  • 在结束时屏蔽_mm512_loadu_pd指令,以避免加载无效内存(https://stackoverflow.com/a/54530225)或对非有限值执行算术。
  • 在任何地方使用64位整数(实际上是有符号的或无符号的)来避免movsxd符号扩展。对于64位系统来说,这通常是一个很好的建议,除非您需要存储大量的索引变量。
  • 使用i!=0而不是i<0作为循环条件来获得jne而不是js,因为这与add指令:https://stackoverflow.com/a/31778403有更好的配对
  • 一些次要的事情,而不是n2=n-r,您也可以计算n2 = n & (-8)n2 = n ^ r。不确定,这是否产生了相关的影响(国际商会似乎不知道或不关心这一点)。天栓-连杆
代码语言:javascript
复制
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);
}

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

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/54809132

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档