SSE / AVX: выберите один из двух векторов с плавающей запятой __m256 на основе минимального и максимального абсолютного значения для каждого элемента - PullRequest
0 голосов
/ 20 сентября 2018

Я ищу эффективную реализацию 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];
}

Т.е. мне нужно поэлементно выбрать a из u и v на основе mask,и в b на основе !mask, где mask = (fabs(u) >= fabs(v)) поэлементно.

Ответы [ 2 ]

0 голосов
/ 20 сентября 2018

clang выполняет довольно разумную работу по автоматической векторизации с -ffast-math и необходимыми квалификаторами __restrict: https://godbolt.org/z/NMvN1u. и обоими входами для их ABS, сравните один раз, vblendvps дважды на исходных входахс той же маской, но с другими источниками в обратном порядке, чтобы получить мин и макс.

Это довольно много, о чем я думал, прежде чем проверять, что делают компиляторы, и смотреть на их вывод, чтобы уточнить детали, которые у меня не было 'пока не продуманоЯ не вижу ничего более умного, чем это.Я не думаю, что мы можем избежать abs () как a, так и b отдельно;нет предиката сравнения 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 интересным образом компилирует оператор return ( Godbolt ):

.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.Это дает нулевое преимущество Skylake-AVX512 для 512-битного vblendmps (обе инструкции с одним мопом для порта 0 или 5), но если таблицы инструкций Agner Fog верны, vblendmps x/y/zmm только когда-либо работает напорт 0 или 5, но замаскированный 256-битный или 128-битный vmovaps x/ymm{k}, x/ymm может работать на любом из p0 / p1 / p5.

Оба имеют задержку одиночного цикла / одного цикла, в отличие от AVX2 vblendvps на основе маски вектор , что составляет 2 моп.( Таким образом, AVX512 является преимуществом даже для 256-битных векторов ).К сожалению, ни один из gcc, clang или ICC не превращает _mm256_cmp_ps в _mm256_cmp_ps_mask и не оптимизирует встроенные функции AVX2 в инструкции AVX512 при компиляции с -march=skylake-avx512.)

s/512/256/, чтобы сделатьверсия minmax_abs512, которая использует AVX512 для 256-битных векторов.


Gcc идет еще дальше и выполняет сомнительную "оптимизацию"

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

вместоиспользуя одну инструкцию смешивания.(Я продолжаю думать, что вижу магазин, за которым следует магазин в маске, но нет, ни один компилятор не смешивается таким образом).

0 голосов
/ 20 сентября 2018

У меня была точно такая же проблема только на днях.Решение, которое я придумал (используя только 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);

Как Питер Кордес предложил в комментариях выше, есть другие подходытак же, как взятие абсолютного значения, за которым следуют мин / макс, а затем повторная вставка знакового бита, но я не смог найти ничего, что было бы с меньшей / меньшей задержкой, чем эта последовательность инструкций.

...