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
вместоиспользуя одну инструкцию смешивания.(Я продолжаю думать, что вижу магазин, за которым следует магазин в маске, но нет, ни один компилятор не смешивается таким образом).