Si vous disposez de SSE4.1, je ne pense pas qu'il soit possible de faire mieux que d'utiliser la fonction pmaxud
+ psubd
approche suggérée par @harold. Avec AVX2, vous pouvez bien sûr aussi utiliser les variantes 256 bits correspondantes.
__m128i subs_epu32_sse4(__m128i a, __m128i b){
__m128i mx = _mm_max_epu32(a,b);
return _mm_sub_epi32(mx, b);
}
Sans SSE4.1, vous devez comparer les deux arguments d'une manière ou d'une autre. Malheureusement, il n'y a pas de epu32
(pas avant AVX512), mais vous pouvez en simuler une en ajoutant d'abord 0x80000000
(ce qui est équivalent à un xor-ing dans ce cas) aux deux arguments :
__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));
}
Dans certains cas, il pourrait Il serait préférable de remplacer la comparaison par un embrouillage du bit le plus élevé et de le diffuser à chaque bit à l'aide d'un décalage (ce qui remplace un pcmpgtd
et de trois opérations bit-logiques (et de devoir charger 0x80000000
au moins une fois) par un psrad
et cinq opérations bit-logiques) :
__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, comprenant également adds_epu32
variantes : https://godbolt.org/z/n4qaW1 Étrangement, clang a besoin de plus de copies de registre que gcc pour les variantes non-SSE4.1. D'autre part, clang trouve le pmaxud
l'optimisation pour le cmpgt_epu32
lorsqu'il est compilé avec SSE4.1 : https://godbolt.org/z/3o5KCm