如何用AVX2向量化优化含前置匹配递减的短固定数组性能关键代码?
AVX2优化方案:简洁实现匹配后批量减1
针对你提出的场景,这里给出更简洁的AVX2实现思路,核心是利用索引向量生成减法掩码+分区域批量操作,避免复杂的逆movemask分支处理:
核心步骤梳理
- 广播匹配值并生成比较掩码:将
w[i]广播到256位向量,与存储v的4个AVX2寄存器分别做相等比较,得到4个比较结果向量。 - 定位第一个匹配位置:通过
_mm256_movemask_epi8将比较向量转成整数掩码,用bsf(或__builtin_ctzll)找到第一个置位的位,得到全局偏移offset(0-127)。 - 分区域生成减法掩码:根据
offset所在的寄存器(0-3),对对应寄存器生成局部减法掩码(仅offset及之后的位置减1),后续寄存器直接用全1掩码(全部位置减1),前面的寄存器用全0掩码(不操作)。 - 批量执行减法:用生成的掩码与对应寄存器做字节减法,完成更新。
具体代码实现示例
#include <immintrin.h> #include <stdint.h> // 预定义固定索引向量(可提前初始化,避免循环内重复生成) static const __m256i idx_vecs[4] = { _mm256_setr_epi8(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31), _mm256_setr_epi8(32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63), _mm256_setr_epi8(64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95), _mm256_setr_epi8(96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127) }; void avx2_optimized(uint8_t v[128], uint8_t w[128]) { // 将v加载到AVX2寄存器 __m256i v_regs[4] = { _mm256_loadu_si256((const __m256i*)&v[0]), _mm256_loadu_si256((const __m256i*)&v[32]), _mm256_loadu_si256((const __m256i*)&v[64]), _mm256_loadu_si256((const __m256i*)&v[96]) }; for (int i = 0; i < 128; ++i) { uint8_t wi = w[i]; __m256i wi_bcast = _mm256_set1_epi8(wi); // 1. 生成4个比较向量 __m256i cmp[4]; for (int r = 0; r <4; ++r) { cmp[r] = _mm256_cmpeq_epi8(v_regs[r], wi_bcast); } // 2. 定位第一个匹配的offset uint32_t mask[4] = { _mm256_movemask_epi8(cmp[0]), _mm256_movemask_epi8(cmp[1]), _mm256_movemask_epi8(cmp[2]), _mm256_movemask_epi8(cmp[3]) }; uint64_t low_mask = (uint64_t)mask[0] | ((uint64_t)mask[1] <<32); uint64_t high_mask = (uint64_t)mask[2] | ((uint64_t)mask[3] <<32); int offset; if (low_mask !=0) { offset = __builtin_ctzll(low_mask); } else { offset = 64 + __builtin_ctzll(high_mask); } int reg_idx = offset /32; int pos_in_reg = offset %32; // 3. 生成各寄存器的减法掩码 __m256i full_sub = _mm256_set1_epi8(0x01); __m256i zero_sub = _mm256_setzero_si256(); __m256i partial_sub = _mm256_and_si256( _mm256_cmpge_epi8(idx_vecs[reg_idx], _mm256_set1_epi8(offset)), full_sub ); __m256i sub_masks[4] = {zero_sub, zero_sub, zero_sub, zero_sub}; // 给对应寄存器设置局部掩码,后续寄存器设置全掩码 sub_masks[reg_idx] = partial_sub; for (int r = reg_idx +1; r <4; ++r) { sub_masks[r] = full_sub; } // 4. 执行批量减法 for (int r =0; r <4; ++r) { v_regs[r] = _mm256_sub_epi8(v_regs[r], sub_masks[r]); } // (如果需要,可在此处处理原代码中//...的逻辑) } // 将寄存器写回内存 _mm256_storeu_si256((__m256i*)&v[0], v_regs[0]); _mm256_storeu_si256((__m256i*)&v[32], v_regs[1]); _mm256_storeu_si256((__m256i*)&v[64], v_regs[2]); _mm256_storeu_si256((__m256i*)&v[96], v_regs[3]); }
方案优势
- 简洁性:无需预定义32种逆movemask掩码,直接用索引向量+比较指令生成局部减法掩码,代码逻辑清晰。
- 低分支:仅在定位offset时有一个简单分支,且分支预测命中率接近100%(每次仅一个掩码有置位),几乎不会造成流水线冲刷。
- 高效性:批量减法操作完全利用AVX2的256位并行能力,后续寄存器直接全减1的操作比生成掩码更高效。
内容的提问来源于stack exchange,提问作者swineone
相关产品推荐
相关产品推荐

