【发布时间】:2021-02-24 20:57:48
【问题描述】:
拥有这种性质的代码:
void foo(double *restrict A, double *restrict x,
double *restrict y) {
y[5] += A[4] * x[5];
y[5] += A[5] * x[1452];
y[5] += A[6] * x[3373];
}
使用gcc 10.2 和标志-O3 -mfma -mavx2 -fvect-cost-model=unlimited (Compiler Explorer) 编译的结果是:
foo(double*, double*, double*):
vmovsd xmm1, QWORD PTR [rdx+40]
vmovsd xmm0, QWORD PTR [rdi+32]
vfmadd132sd xmm0, xmm1, QWORD PTR [rsi+40]
vmovsd xmm2, QWORD PTR [rdi+40]
vfmadd231sd xmm0, xmm2, QWORD PTR [rsi+11616]
vmovsd xmm3, QWORD PTR [rdi+48]
vfmadd231sd xmm0, xmm3, QWORD PTR [rsi+26984]
vmovsd QWORD PTR [rdx+40], xmm0
ret
它不会将任何数据打包在一起(4 个vmovsd 用于加载数据,1 个用于存储),执行 3 个vfmaddXXXsd。尽管如此,我将其矢量化的动机是它可以只使用一个vfmadd231pd 来完成。我使用 AVX2 的内在函数编写此代码的“最干净”尝试是:
void foo_intrin(double *restrict A, double *restrict x,
double *restrict y) {
__m256d __vop0, __vop1,__vop2;
__m128d __lo256, __hi256;
// THE ISSUE
__vop0 = _mm256_maskload_pd(&A[4], _mm256_set_epi64x(0,-1,-1,-1));
__vop1 = _mm256_mask_i64gather_pd(_mm256_setzero_pd(), &x[5],
_mm256_set_epi64x(0,3368, 1447, 0),
_mm256_set_pd(0,-1,-1,-1), 8);
// 1 vs 3 FMADD, "the gain"
__vop2 = _mm256_fmadd_pd(__vop0, __vop1, __vop2);
// reducing 4 double elements:
// Peter Cordes' answer https://stackoverflow.com/a/49943540/2856041
__lo256 = _mm256_castpd256_pd128(__vop2);
__hi256 = _mm256_extractf128_pd(__vop2, 0x1);
__lo256 = _mm_add_pd(__lo256, __hi256);
// question:
// could you use here shuffle instead?
// __hi256 = _mm_shuffle_pd(__lo256, __lo256, 0x1);
__hi256 = _mm_unpackhi_pd(__lo256, __lo256);
__lo256 = _mm_add_pd(__lo256, __hi256);
y[5] += __lo256[0];
}
生成以下 ASM:
foo_intrin(double*, double*, double*):
vmovdqa ymm2, YMMWORD PTR .LC1[rip]
vmovapd ymm3, YMMWORD PTR .LC2[rip]
vmovdqa ymm0, YMMWORD PTR .LC0[rip]
vmaskmovpd ymm1, ymm0, YMMWORD PTR [rdi+32]
vxorpd xmm0, xmm0, xmm0
vgatherqpd ymm0, QWORD PTR [rsi+40+ymm2*8], ymm3
vxorpd xmm2, xmm2, xmm2
vfmadd132pd ymm0, ymm2, ymm1
vmovapd xmm1, xmm0
vextractf128 xmm0, ymm0, 0x1
vaddpd xmm0, xmm0, xmm1
vunpckhpd xmm1, xmm0, xmm0
vaddpd xmm0, xmm0, xmm1
vaddsd xmm0, xmm0, QWORD PTR [rdx+40]
vmovsd QWORD PTR [rdx+40], xmm0
vzeroupper
ret
.LC0:
.quad -1
.quad -1
.quad -1
.quad 0
.LC1:
.quad 0
.quad 1447
.quad 3368
.quad 0
.LC2:
.long 0
.long -1074790400
.long 0
.long -1074790400
.long 0
.long -1074790400
.long 0
.long 0
对不起,如果有人现在有焦虑症,我深表歉意。让我们分解一下:
- 我猜那些
vxorpd是用来清理寄存器的,但icc只生成一个,而不是两个。 - 根据Agner Fog,VCL 在AVX2 中不使用
maskload,因为“屏蔽指令在AVX512 之前的指令集中非常慢”。然而,在uops.info 中,据报道,对于 Skylake(“常规”,无 AVX-512),:- VMOVAPD (YMM, M256),例如
_mm256_load_pd的延迟为 [≤5;≤8],吞吐量为 0.5。 - VMASKMOVPD(YMM、YMM、M256),例如
_mm256_maskload_pd具有延迟 [1;≤9] 和 0.5 的吞吐量,但在两个微指令中解码而不是一个。这个差距有这么大吗?以不同的方式打包会更好吗?
- VMOVAPD (YMM, M256),例如
- 关于
mask_gather-fashion 说明,据我了解,上面所有文档中,无论是否使用掩码,它都提供相同的性能,这是正确的吗? uops.info 和Intel Intrinsics Guide 都报告了相同的性能和 ASM 形式;我很可能遗漏了一些东西。- 在所有情况下,
gather是否比“简单”set更好?用内在术语说话。我知道set会根据数据类型生成vmov类型的指令(例如,如果数据是常量,它可能只加载一个地址,如.LC0、.LC1和.LC2)。李>
- 在所有情况下,
- 根据 Intel Intrinsics,
_mm256_shuffle_pd和_mm256_unpackhi_pd具有相同的 lantecy 和吞吐量;第一个生成vpermildp,第二个生成vunpckhpd,并且uops.info 也报告相同的值。有什么区别吗?
最后但同样重要的是,这种特殊矢量化值得吗?我的意思不是我的内在代码,而是像这样矢量化代码的概念。我怀疑有太多的数据移动来执行比较干净的代码编译器,一般来说,产生,所以我关心的是改进打包非连续数据的方式。
【问题讨论】:
标签: x86 simd intrinsics avx avx2