4

我正在寻找高效的 AVX (AVX512) 实现

// Given
float u[8];
float v[8];

// Compute
float a[8];
float b[8];

//  Such that
for ( int i = 0; i < 8; ++i )
{
    a[i] = fabs(u[i]) >= fabs(v[i]) ? u[i] : v[i];
    b[i] = fabs(u[i]) <  fabs(v[i]) ? u[i] : v[i];
}

即,我需要按元素选择afromuvbased on mask,以及 into bbased on !mask, where mask = (fabs(u) >= fabs(v))element-wise 。

4

2 回答 2

5

前几天我遇到了同样的问题。我想出的解决方案(仅使用 AVX)是:

// take the absolute value of u and v
__m256 sign_bit = _mm256_set1_ps(-0.0f);
__m256 u_abs = _mm256_andnot_ps(sign_bit, u);
__m256 v_abs = _mm256_andnot_ps(sign_bit, v);
// get a mask indicating the indices for which abs(u[i]) >= abs(v[i])
__m256 u_ge_v = _mm256_cmp_ps(u_abs, v_abs, _CMP_GE_OS);
// use the mask to select the appropriate elements into a and b, flipping the argument
// order for b to invert the sense of the mask
__m256 a = _mm256_blendv_ps(u, v, u_ge_v);
__m256 b = _mm256_blendv_ps(v, u, u_ge_v);

AVX512 等效项为:

// take the absolute value of u and v
__m512 sign_bit = _mm512_set1_ps(-0.0f);
__m512 u_abs = _mm512_andnot_ps(sign_bit, u);
__m512 v_abs = _mm512_andnot_ps(sign_bit, v);
// get a mask indicating the indices for which abs(u[i]) >= abs(v[i])
__mmask16 u_ge_v = _mm512_cmp_ps_mask(u_abs, v_abs, _CMP_GE_OS);
// use the mask to select the appropriate elements into a and b, flipping the argument
// order for b to invert the sense of the mask
__m512 a = _mm512_mask_blend_ps(u_ge_v, u, v);
__m512 b = _mm512_mask_blend_ps(u_ge_v, v, u);

正如 Peter Cordes 在上面的评论中所建议的那样,还有其他方法,比如取绝对值,然后是最小值/最大值,然后重新插入符号位,但我找不到比这个序列更短/更低延迟的东西指示。

于 2018-09-20T00:28:41.490 回答
3

-ffast-mathclang 使用必要的__restrict限定符 对它进行了自动矢量化,这是一项非常合理的工作: https ://godbolt.org/z/NMvN1u 。以及 ABS 的两个输入,在具有相同掩码的原始输入上比较一次、vblendvps两次,但其他源以相反的顺序获得最小值和最大值。

这几乎就是我在检查编译器所做的工作之前的想法,并查看它们的输出以确认我尚未考虑过的细节。我看不出有什么比这更聪明的了。我认为我们不能避免分别对 a 和 b 进行 abs() 处理;没有cmpps比较谓词可以比较大小并忽略符号位。

// untested: I *might* have reversed min/max, but I think this is right.
#include <immintrin.h>
// returns min_abs
__m256 minmax_abs(__m256 u, __m256 v,  __m256 *max_result) {
    const __m256 signbits = _mm256_set1_ps(-0.0f);
    __m256 abs_u = _mm256_andnot_ps(signbits, u);
    __m256 abs_v = _mm256_andnot_ps(signbits, v);  // strip the sign bit

    __m256 maxabs_is_v = _mm256_cmp_ps(abs_u, abs_v, _CMP_LT_OS);  // u < v

    *max_result = _mm256_blendv_ps(v, u, maxabs_is_v);
    return        _mm256_blendv_ps(u, v, maxabs_is_v);
}

你会对 AVX512 做同样的事情,只是你比较的是一个掩码而不是另一个向量。

// returns min_abs
__m512 minmax_abs512(__m512 u, __m512 v,  __m512 *max_result) {
    const __m512 absmask = _mm512_castsi512_ps(_mm512_set1_epi32(0x7fffffff));
    __m512 abs_u = _mm512_and_ps(absmask, u);
    __m512 abs_v = _mm512_and_ps(absmask, v);  // strip the sign bit

    __mmask16 maxabs_is_v = _mm512_cmp_ps_mask(abs_u, abs_v, _CMP_LT_OS);  // u < v

    *max_result = _mm512_mask_blend_ps(maxabs_is_v, v, u);
    return        _mm512_mask_blend_ps(maxabs_is_v, u, v);
}

Clang 以一种有趣的方式(Godbolt)编译 return 语句:

.LCPI2_0:
    .long   2147483647              # 0x7fffffff
minmax_abs512(float __vector(16), float __vector(16), float __vector(16)*):           # @minmax_abs512(float __vector(16), float __vector(16), float __vector(16)*)
    vbroadcastss    zmm2, dword ptr [rip + .LCPI2_0]
    vandps  zmm3, zmm0, zmm2
    vandps  zmm2, zmm1, zmm2
    vcmpltps        k1, zmm3, zmm2
    vblendmps       zmm2 {k1}, zmm1, zmm0
    vmovaps zmmword ptr [rdi], zmm2   ## store the blend result
    vmovaps zmm0 {k1}, zmm1           ## interesting choice: blend merge-masking
    ret

而不是使用另一个vblendmps,clang 通知zmm0已经有一个混合输入,并使用带有常规向量的合并掩码vmovaps。这对于 512 位vblendmps(端口 0 或 5 的单 uop 指令)具有零优势,但如果Agner Fog 的指令表是正确的,则vblendmps x/y/zmm只能在端口 0 或 5 上运行,但屏蔽的 256 位或128 位vmovaps x/ymm{k}, x/ymm可以在任何 p0/p1/p5 上运行。

两者都是单微指令/单周期延迟,与vblendvps基于 2 微指令的掩码向量的 AVX2 不同。(所以 AVX512 即使对于 256 位向量也是一个优势)。不幸的是,gcc、clang 或 ICC_mm256_cmp_ps_mm256_cmp_ps_mask使用-march=skylake-avx512.

s/512/256/制作一个minmax_abs512将 AVX512 用于 256 位向量的版本。


Gcc 走得更远,并做了有问题的“优化”

    vmovaps zmm2, zmm1        # tmp118, v
    vmovaps zmm2{k1}, zmm0    # tmp118, tmp114, tmp118, u

而不是使用一个混合指令。(我一直在想我看到的是一个商店,然后是一个蒙面商店,但是不,编译器都没有以这种方式混合)。

于 2018-09-20T00:45:08.593 回答