如何用变量实现AVX洗牌?寻求_mm_shuffle_epi32替代优化方案
优化固定查找表搜索的SIMD实现
你的核心需求是用SIMD指令替代标量循环,提升批量处理vindex的性能,以下是几种更优的处理方式,基于不同指令集兼容性:
1. 基于AVX2的并行优化(推荐,吞吐量最高)
利用AVX2的256位向量指令,一次处理8个int8_t索引,批量生成对应查找结果:
步骤说明
- 准备查找表向量:将4个
int32_t的表元素广播到256位向量中:__m256i table_vec = _mm256_setr_epi32(t0, t1, t2, t3, t0, t1, t2, t3); - 批量加载索引:每次加载8个
int8_t索引到128位向量(高8字节可忽略):for (uint32_t i = 0; i < 100; i += 8) { __m128i idx_vec = _mm_loadu_si128((__m128i*)&vindex[i]); - 提取每个索引的4个2-bit片段:通过字节级移位和掩码操作,分离出每个索引的4组2-bit索引:
__m128i idx0 = _mm_and_si128(idx_vec, _mm_set1_epi8(0x03)); // 低2位 __m128i idx1 = _mm_and_si128(_mm_srli_epi8(idx_vec, 2), _mm_set1_epi8(0x03)); // 右移2位后的2位 __m128i idx2 = _mm_and_si128(_mm_srli_epi8(idx_vec, 4), _mm_set1_epi8(0x03)); // 右移4位后的2位 __m128i idx3 = _mm_and_si128(_mm_srli_epi8(idx_vec, 6), _mm_set1_epi8(0x03)); // 右移6位后的2位 - 扩展索引并查表:将8位索引扩展为32位,用
_mm256_permutevar_epi32(支持向量索引的洗牌指令,替代需要立即数的_mm_shuffle_epi32)批量查表:__m256i idx0_32 = _mm256_cvtepi8_epi32(idx0); __m256i idx1_32 = _mm256_cvtepi8_epi32(idx1); __m256i idx2_32 = _mm256_cvtepi8_epi32(idx2); __m256i idx3_32 = _mm256_cvtepi8_epi32(idx3); __m256i res0 = _mm256_permutevar_epi32(table_vec, idx0_32); __m256i res1 = _mm256_permutevar_epi32(table_vec, idx1_32); __m256i res2 = _mm256_permutevar_epi32(table_vec, idx2_32); __m256i res3 = _mm256_permutevar_epi32(table_vec, idx3_32); - 重新排列结果并存储:将4组结果向量交错排列,匹配
pdata的连续存储格式,然后批量写入内存:// 交错排列结果:res0[0], res1[0], res2[0], res3[0], res0[1], ..., res3[7] __m256i tmp0 = _mm256_unpacklo_epi32(res0, res1); __m256i tmp1 = _mm256_unpacklo_epi32(res2, res3); __m256i tmp2 = _mm256_unpackhi_epi32(res0, res1); __m256i tmp3 = _mm256_unpackhi_epi32(res2, res3); __m256i final0 = _mm256_unpacklo_epi64(tmp0, tmp1); __m256i final1 = _mm256_unpackhi_epi64(tmp0, tmp1); __m256i final2 = _mm256_unpacklo_epi64(tmp2, tmp3); __m256i final3 = _mm256_unpackhi_epi64(tmp2, tmp3); // 批量存储到pdata _mm256_storeu_si256((__m256i*)&pdata[i*4], final0); _mm256_storeu_si256((__m256i*)&pdata[i*4 + 32], final1); _mm256_storeu_si256((__m256i*)&pdata[i*4 + 64], final2); _mm256_storeu_si256((__m256i*)&pdata[i*4 + 96], final3); } - 处理剩余元素:最后对不足8个的剩余索引(100%8=4个),用原标量循环或简化的SIMD代码处理。
2. 基于SSE4.1的兼容方案
如果仅支持SSE4.1指令集,用128位向量一次处理4个索引,逻辑和AVX2版本类似,核心差异是用_mm_permutevar_epi32替代256位指令,每次处理4个vindex元素,最终吞吐量略低于AVX2版本,但仍优于标量循环。
3. 字节级查表优化(适合小范围结果)
如果table的int32_t元素低8位即可满足需求(或可转换为字节级存储),可以用_mm_shuffle_epi8(SSE2指令)实现字节级批量查表:
- 将
table转换为字节向量:__m128i table_byte = _mm_setr_epi8(t0&0xFF, t0&0xFF, t0&0xFF, t0&0xFF, t1&0xFF, ..., t3&0xFF);(每个元素重复4次,对应32位的4个字节) - 提取索引后直接用
_mm_shuffle_epi8查表,再将字节结果扩展为32位。这种方式指令延迟更低,但仅适用于结果可压缩为字节的场景。
性能对比
- 标量循环:每次处理1个索引,生成4个32位结果,受CPU分支预测和内存访问限制,吞吐量较低。
- SIMD优化版本:一次处理8/4个索引,并行完成查表和结果排列,吞吐量可提升3-7倍(取决于CPU和指令集),远优于原循环与
_mm_shuffle_epi32相当的性能。
内容的提问来源于stack exchange,提问作者user9082561
相关产品推荐
相关产品推荐

