我受到此链接的启发 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);
}
我认为使用BMI1和/或BMI2指令可以生成较少指令的掩码。但是,
__mmask8 mask = _bextr_u32(-1, 0, r)
(在指令数量上)没有比
更好__mmask8 mask = (1 << r) -1;
请参见https://godbolt.org/z/BFQCM3和https://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.
我认为,如果链接的作者从-n
到0
,而不是从0
到n
,他们可能会跳过cmp
就像我在主循环中使用的指令一样(请参见下面的程序集),因此对于AVX,应该在主循环中使用5条指令。
这里是带有ICC19和-O3 -xCOMMON-AVX512
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
其中
add r8, 8
js ..B1.3
应将宏运算符融合到一条指令中。但是,正如Peter Cordes in this answer所指出的, js无法融合。编译器本来可以生成jl
,但会融合在一起。
我使用了Agner Fog的testp实用程序来获取核心时钟(不是参考时钟),指令以及已退休的产品。我针对SSE2(实际上是带有FMA但具有128位向量的AVX2),AVX2和AVX512进行了三种不同的循环
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
成比例。
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,微指令的数量大致相同,但对于v3,微指令的数量下降。对于v1和v2,它与7*n/vec_size
成正比,对于v3 6*n/vec_size
。
这是IACA3针对vec_size = 2的结果
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
和add
的宏熔丝与Agner和testp
实用程序的性能计数器不符。如上文所述,v2与7*n/vec_size
成比例,v3与6*n/vec_size
成比例,我推断这意味着js
没有宏熔丝。
我认为除了说明数量外,链接的作者还应该考虑核心周期,甚至可能是微不足道。
答案 0 :(得分:4)
如果使用以下BMI2内部函数,则可以保存一条指令:
__mmask8 mask = _bzhi_u32(-1, r);
而不是__mmask8 mask = (1 << r) -1;
。参见Godbolt link。
bzhi
instruction将高位从指定位置开始归零。对于寄存器操作数,bzhi
的等待时间为1个周期,吞吐量为每个周期2个。
答案 1 :(得分:4)
除了@wim使用df2.mul(df.Value.reset_index('Location',drop=True))
Out[683]:
2000 2001 2002
Country Unit Location
US USD Hawai 799.2 NaN 149.6
IT EUR Torino 1065.6 NaN 187.0
FR EUR Paris 932.4 NaN 224.4
而不是_bzhi_u32
的答案之外,您还应该:
_bextr_u32
指令,以避免加载无效的内存(https://stackoverflow.com/a/54530225)或对非限定值进行算术运算。_mm512_loadu_pd
符号扩展。通常,在64位系统上,这是一个很好的建议,除非您需要存储很多索引变量。movsxd
而不是i!=0
作为循环条件来获得i<0
而不是jne
,因为这更好地与js
指令配对使用:{{ 3}} add
或n2=n-r
而不是n2 = n & (-8)
。不确定,这是否有重要的关系(icc似乎不知道或不在乎)。 https://stackoverflow.com/a/31778403 n2 = n ^ r
要进一步减少指令的数量,可以使用指针递增,例如Godbolt-Link(但是这会增加循环内的指令)。