Если у вас есть SSE4.1, я не думаю, что вы можете добиться большего успеха, чем использовать подход pmaxud
+ psubd
, предложенный @harold.С AVX2 вы, конечно, также можете использовать соответствующие 256-битные варианты.
__m128i subs_epu32_sse4(__m128i a, __m128i b){
__m128i mx = _mm_max_epu32(a,b);
return _mm_sub_epi32(mx, b);
}
Без SSE4.1 вам нужно каким-то образом сравнить оба аргумента.К сожалению, нет epu32
сравнения (не до AVX512), но вы можете смоделировать его, сначала добавив 0x80000000
(что эквивалентно xoring в этом случае) к обоим аргументам:
__m128i cmpgt_epu32(__m128i a, __m128i b) {
const __m128i highest = _mm_set1_epi32(0x80000000);
return _mm_cmpgt_epi32(_mm_xor_si128(a,highest),_mm_xor_si128(b,highest));
}
__m128i subs_epu32(__m128i a, __m128i b){
__m128i not_saturated = cmpgt_epu32(a,b);
return _mm_and_si128(not_saturated, _mm_sub_epi32(a,b));
}
В некоторых случаях может быть лучше заменить сравнение некоторым сдвигом битов старшего бита и передачей его каждому биту с использованием сдвига (это заменяет pcmpgtd
и три операции битовой логики(и необходимо загрузить 0x80000000
хотя бы один раз) с помощью psrad
и пяти операций с битовой логикой):
__m128i subs_epu32_(__m128i a, __m128i b) {
__m128i r = _mm_sub_epi32(a,b);
__m128i c = (~a & b) | (r & ~(a^b)); // works with gcc/clang. Replace by corresponding intrinsics, if necessary (note that `andnot` is a single instruction)
return _mm_srai_epi32(c,31) & r;
}
Godbolt-Link, также включая adds_epu32
варианты: https://godbolt.org/z/n4qaW1 Странно, но для Clang требуется больше регистровых копий, чем для GCC для не-SSE4.1 вариантов.С другой стороны, clang находит оптимизацию pmaxud
для варианта cmpgt_epu32
при компиляции с SSE4.1: https://godbolt.org/z/3o5KCm