如果您拥有BMI2,请使用以下版本。
__m128i compressZeroIndices_bmi2( __m128i v )
{
const __m128i zero = _mm_setzero_si128();
v = _mm_cmpeq_epi8( v, zero );
uint64_t low = (uint64_t)_mm_cvtsi128_si64( v );
uint64_t high = (uint64_t)_mm_extract_epi64( v, 1 );
v = _mm_sub_epi8( zero, v );
v = _mm_sad_epu8( v, zero );
v = _mm_add_epi64( v, _mm_srli_si128( v, 8 ) );
v = _mm_shuffle_epi8( v, zero );
const __m128i identity = _mm_setr_epi8( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
v = _mm_max_epu8( v, identity );
__m128i mask = _mm_cmpeq_epi8( v, identity );
uint64_t lowBits = std::popcount( low );
low = _pext_u64( 0x0706050403020100ull, low );
high = _pext_u64( 0x0F0E0D0C0B0A0908ull, high );
v = _mm_cvtsi64_si128( low | ( high << lowBits ) );
v = _mm_insert_epi64( v, high >> ( 64 - lowBits ), 1 );
return _mm_or_si128( v, mask );
}
这是一个没有使用BMI2指令集的另一版本。在大多数CPU上可能会更慢,但代码更简单,并且不使用任何标量指令。
inline __m128i sortStep( __m128i a, __m128i perm, __m128i blend )
{
__m128i b = _mm_shuffle_epi8( a, perm );
__m128i i = _mm_min_epu8( a, b );
__m128i ax = _mm_max_epu8( a, b );
return _mm_blendv_epi8( i, ax, blend );
}
__m128i compressZeroIndices( __m128i v )
{
v = _mm_cmpgt_epi8( v, _mm_setzero_si128() );
const __m128i identity = _mm_setr_epi8( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
v = _mm_or_si128( v, identity );
const __m128i perm1 = _mm_setr_epi8( 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14 );
const __m128i blend1 = _mm_set1_epi16( (short)0xFF00 );
const __m128i perm2 = _mm_setr_epi8( 0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 15 );
const __m128i blend2 = _mm_setr_epi8( 0, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0 );
for( size_t i = 0; i < 8; i++ )
{
v = sortStep( v, perm1, blend1 );
v = sortStep( v, perm2, blend2 );
}
return v;
}
提示:如果您想获取输出向量的长度,请使用此函数:
uint32_t vectorLength( __m128i v )
{
uint32_t mask = (uint32_t)_mm_movemask_epi8( v );
mask |= 0x10000;
return _tzcnt_u32( mask );
}
pdep
/pext
吗? 你可能希望使用BMI2pext
来进行每次8字节的左包装操作,类似于AVX2,基于掩码打包的最有效方法是什么? - 没有AVX512,您没有SIMD左包装,并且一个2^16个__m128i
洗牌掩码表显然是可怕的。 (或者我想说就是常量传播后的最终结果的__m128i
)。但无论如何,64K x 16字节将是一个巨大的查找表,几乎每次都会缺失缓存。 - Peter Cordes0
和1
而不是通常的0
/0xff
有点奇怪。请注意,Soonts 的答案通过比较将您的1
转换为0xff
。如果您的先前代码自然产生 0/1 而不是 0/-1,那么就没问题了,但如果您需要额外的工作来实现这一点,请不要这样做。 - Peter Cordes