Sin*_*bag 3 c++ double sse simd vectorization
I would like to make some vector computation faster, and I believe that SIMD instructions for float comparison and manipulation could help, here is the operation:
void func(const double* left, const double* right, double* res, const size_t size, const double th, const double drop) {
for (size_t i = 0; i < size; ++i) {
res[i] = right[i] >= th ? left[i] : (left[i] - drop) ;
}
}
Run Code Online (Sandbox Code Playgroud)
Mainly, it drops the left
value by drop
in case right
value is higher than threshold
.
The size is around 128-256 (not that big), but computation is called heavily.
I tried to start with loop unrolling, but did not win a lot of performance, but may be some compile instructions are needed.
Could you please suggest some improvement into the code for faster computation?
Clang已经按照Soonts建议的手动方式进行了自动矢量化。__restrict
在指针上 使用,因此不需要后备版本,该版本可用于某些数组之间的重叠。它仍然会自动矢量化,但会使功能肿。
不幸的是,gcc仅使用进行自动矢量化-ffast-math
。事实证明,这仅-fno-trapping-math
是必需的:通常这是安全的,特别是如果您不使用fenv
访问权限来揭露任何FP异常(feenableexcept
)或查看MXCSR粘性FP异常标志(fetestexcept
)。
使用该选项,那么GCC也将(v)pblendvpd
与-march=nehalem
或一起使用-march=znver1
。 在Godbolt上看到
此外,您的C函数已损坏。 th
并且drop
是标量双精度,但您将其声明为const double *
AVX512F允许您进行!(right[i] >= thresh)
比较,并将结果掩码用于合并掩码减法。
谓词为true的left[i] - drop
元素将获得,其他元素将保留其left[i]
值,因为您将info合并为left
值的向量。
不幸的是,GCC -march=skylake-avx512
使用法线vsubpd
,然后使用单独的法线vmovapd zmm2{k1}, zmm5
进行混合,这显然是错过的优化方法。混合目标已经是SUB的输入之一。
将AVX512VL用于256位向量(以防程序的其余部分无法有效使用512位,因此您的Turbo时钟速度不会降低):
__m256d left = ...;
__m256d right = ...;
__mmask8 cmp = _mm256_cmp_pd_mask(right, set1(th), _CMP_NGE_UQ);
__m256d res = _mm256_mask_sub_pd (left, cmp, left, set1(drop));
Run Code Online (Sandbox Code Playgroud)
因此(除加载和存储外)这是AVX512F / VL的2条指令。
而且它对所有编译器都更有效,因为您只需要AND,而不是变量混合。 因此,仅使用SSE2会明显更好,并且即使在大多数CPU支持SSE4.1的情况下也是如此blendvpd
,因为该指令的效率不高。
您可以减去0.0
或drop
从left[i]
基于比较结果。
0.0
根据比较结果产生或常量非常有效:只需一条andps
指令即可。(的位模式为0.0
全零,SIMD会比较全1或全0位的产生向量。因此,AND会将旧值保留或将其清零。)
我们也可以加-drop
而不是减drop
。这会在输入上造成额外的否定,但是使用AVX可以使用的存储源操作数vaddpd
。GCC选择使用索引寻址模式,因此实际上并没有帮助减少Intel CPU的前端uop数量。它将“分层”。但是即使使用-ffast-math
,gcc也不会自行进行此优化以允许折叠负载。(不过,除非我们展开循环,否则不应该单独进行指针增量操作。)
void func3(const double *__restrict left, const double *__restrict right, double *__restrict res,
const size_t size, const double th, const double drop)
{
for (size_t i = 0; i < size; ++i) {
double add = right[i] >= th ? 0.0 : -drop;
res[i] = left[i] + add;
}
}
Run Code Online (Sandbox Code Playgroud)
上面的Godbolt链接提供的GCC 9.1的内部循环(不带任何-march
选项,不带-ffast-math
):
# func3 main loop
# gcc -O3 -march=skylake (without fast-math)
.L33:
vcmplepd ymm2, ymm4, YMMWORD PTR [rsi+rax]
vandnpd ymm2, ymm2, ymm3
vaddpd ymm2, ymm2, YMMWORD PTR [rdi+rax]
vmovupd YMMWORD PTR [rdx+rax], ymm2
add rax, 32
cmp r8, rax
jne .L33
Run Code Online (Sandbox Code Playgroud)
或普通的SSE2版本具有一个内部循环,基本上与with相同,left - zero_or_drop
而不是left + zero_or_minus_drop
,因此除非您可以保证编译器以16字节对齐或正在制作AVX版本,否则否定drop
只是额外的开销。
否定drop
需要不断从内存(异或符号位),这是唯一的静态常量这个功能需求,使代价是值得考虑你的情况下循环不运行的时候一个巨大的数字。(除非th
或drop
在内联之后还是编译时常量,并且无论如何都会被加载。或者特别是如果-drop
可以在编译时进行计算。或者是否可以使程序与负数一起使用drop
。)
加法和减法之间的另一个区别是减法不会破坏零的符号。 -0.0 - 0.0 = -0.0
,+0.0 - 0.0 = +0.0
。以防万一。
# gcc9.1 -O3
.L26:
movupd xmm5, XMMWORD PTR [rsi+rax]
movapd xmm2, xmm4 # duplicate th
movupd xmm6, XMMWORD PTR [rdi+rax]
cmplepd xmm2, xmm5 # destroy the copy of th
andnpd xmm2, xmm3 # _mm_andnot_pd
addpd xmm2, xmm6 # _mm_add_pd
movups XMMWORD PTR [rdx+rax], xmm2
add rax, 16
cmp r8, rax
jne .L26
Run Code Online (Sandbox Code Playgroud)
GCC使用未对齐的负载,因此(没有AVX)它无法将内存源操作数折叠为cmppd
或subpd
归档时间: |
|
查看次数: |
280 次 |
最近记录: |