问题描述
PCMPGTQ 在 SSE2 上不存在,也不适用于无符号整数。我们的目标是为无符号 64 位比较提供向后兼容的解决方案,以便我们可以将它们包含到 WebAssembly SIMD 标准中。
这是 ARMv7+NEON 的姐妹问题: What is the most efficient way to do SIMD unsigned 64 bit comparison (CMHS) on ARMv7 with NEON?
并且与已经为 SSE2 和 Neon 回答的签名比较变体的问题相关:
How to simulate pcmpgtq on sse2?
What is the most efficient way to support CMGT with 64bit signed comparisons on ARMv7a with Neon?
解决方法
给你。
__m128i cmpgt_epu64_sse2( __m128i a,__m128i b )
{
// Compare uint32_t lanes for a > b and a < b
const __m128i signBits = _mm_set1_epi32( 0x80000000 );
a = _mm_xor_si128( a,signBits );
b = _mm_xor_si128( b,signBits );
__m128i gt = _mm_cmpgt_epi32( a,b );
__m128i lt = _mm_cmpgt_epi32( b,a );
// It's too long to explain why,but the result we're after is equal to ( gt > lt ) for uint64_t lanes of these vectors.
// Unlike the source numbers,lt and gt vectors contain a single bit of information per 32-bit lane.
// This way it's much easier to compare them with SSE2.
// Clear the highest bit to avoid overflows of _mm_sub_epi64.
// _mm_srli_epi32 by any number of bits in [ 1 .. 31 ] would work too,only slightly slower.
gt = _mm_andnot_si128( signBits,gt );
lt = _mm_andnot_si128( signBits,lt );
// Subtract 64-bit integers; we're after the sign bit of the result.
// ( gt > lt ) is equal to extractSignBit( lt - gt )
// The above is only true when ( lt - gt ) does not overflow,that's why we can't use it on the source numbers.
__m128i res = _mm_sub_epi64( lt,gt );
// Arithmetic shift to broadcast the sign bit into higher halves of uint64_t lanes
res = _mm_srai_epi32( res,31 );
// Broadcast higher 32-bit lanes into the final result.
return _mm_shuffle_epi32( res,_MM_SHUFFLE( 3,3,1,1 ) );
}
如果 SSE3 可用,movshdup
也是一个不错的选择,而不是 pshufd
(_mm_shuffle_epi32) 将 srai 结果复制到每个元素中的低 dword。 (或者,如果下一次使用是 movmskpd
或其他仅取决于每个 qword 的高位部分的内容,则将其优化掉)。
例如,在 Conroe/Merom(第一代 Core 2、SSSE3 和大多数 SIMD 执行单元是 128 位宽,但 shuffle 单元有限制)上,pshufd
是 2 uops,3 周期延迟(flt- >int 域)。 movshdup
只有 1 uop、1 个周期的延迟,因为它的硬连线 shuffle 仅在每个 64 位寄存器的一半内。 movshdup
在“SIMD-int”域中运行,因此与 pshufd
不同,它不会在整数移位和您接下来执行的任何整数操作之间造成任何额外的旁路延迟。 (https://agner.org/optimize/)
如果你是 JITing,你只会在没有 SSE4.2 的 CPU 上使用它,这意味着 Intel 在 Nehalem 之前,AMD 在 Bulldozer 之前。请注意,在某些 CPU 上,psubq
(_mm_sub_epi64
) 比窄的 psub
稍慢,但它仍然是最佳选择。
为了完整起见,这里是 SSSE3 版本(与 SSE3 不太一样),以恒定负载为代价节省了一些指令。确定它是更快还是更慢的唯一方法 - 在旧计算机上测试。
__m128i cmpgt_epu64_ssse3( __m128i a,a );
// Shuffle bytes making two pairs of equal uint32_t values to compare.
// Each uint32_t combines two bytes from lower and higher parts of the vectors.
const __m128i shuffleIndices = _mm_setr_epi8(
0,4,-1,8,12,-1 );
gt = _mm_shuffle_epi8( gt,shuffleIndices );
lt = _mm_shuffle_epi8( lt,shuffleIndices );
// Make the result
return _mm_cmpgt_epi32( gt,lt );
}