AVX2是否存在将16位值广播加载16次至__m256i寄存器的指令及内在函数?
回答:AVX2实现16位值广播到__m256i寄存器
当然有!AVX2正好提供了专门的内在函数来完成你需要的**将单个16位值广播填充到__m256i寄存器的所有16位分量(共16次)**的操作,完全可以替代你代码里的loadLow16函数。
核心解决方案:_mm256_set1_epi16内在函数
AVX2的_mm256_set1_epi16(uint16_t a)内在函数会接收一个16位无符号整数,然后将其复制到__m256i寄存器的每一个16位元素中(256位寄存器恰好包含16个16位元素)。对应的硬件指令是vpbroadcastw,只需要单条指令就能完成操作,效率非常高。
修正你的代码
先指出你代码里的几个小问题,再给出完整的修正版本:
- 你的
masks数组最后一个元素写错了:应该是1<<15(16位无符号数的最高位是第15位,从0开始计数),而不是1<<16。 - 循环里的
vals是笔误,应该替换为对应的v1/v2/v3/v4。 - 用
_mm256_set1_epi16替换loadLow16。
修正后的完整代码:
#include <immintrin.h> #include <stdint.h> void countHistBits4(const uint64_t p[], uint32_t n, uint32_t hist[64]) { // 将mask数组设为静态常量并32字节对齐,优化加载效率 static const uint16_t masks[16] __attribute__((aligned(32))) = { 1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7, 1<<8, 1<<9, 1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15 }; __m256i mask = _mm256_load_si256((const __m256i*)masks); __m256i count1 = _mm256_setzero_si256(); __m256i count2 = _mm256_setzero_si256(); __m256i count3 = _mm256_setzero_si256(); __m256i count4 = _mm256_setzero_si256(); for (uint32_t i = 0; i < n; i++) { uint64_t val = p[i]; // 用_mm256_set1_epi16实现16位值的广播加载 __m256i v1 = _mm256_set1_epi16((uint16_t)(val & 0xFFFF)); __m256i v2 = _mm256_set1_epi16((uint16_t)((val >> 16) & 0xFFFF)); __m256i v3 = _mm256_set1_epi16((uint16_t)((val >> 32) & 0xFFFF)); __m256i v4 = _mm256_set1_epi16((uint16_t)((val >> 48) & 0xFFFF)); // 按位与mask后累加统计 v1 = _mm256_and_si256(v1, mask); count1 = _mm256_adds_epi16(count1, v1); v2 = _mm256_and_si256(v2, mask); count2 = _mm256_adds_epi16(count2, v2); v3 = _mm256_and_si256(v3, mask); count3 = _mm256_adds_epi16(count3, v3); v4 = _mm256_and_si256(v4, mask); count4 = _mm256_adds_epi16(count4, v4); } // 将寄存器中的统计结果导出到hist数组 uint16_t tmp[16]; _mm256_storeu_si256((__m256i*)tmp, count1); for (int j = 0; j < 16; j++) hist[j] += tmp[j]; _mm256_storeu_si256((__m256i*)tmp, count2); for (int j = 0; j < 16; j++) hist[j+16] += tmp[j]; _mm256_storeu_si256((__m256i*)tmp, count3); for (int j = 0; j < 16; j++) hist[j+32] += tmp[j]; _mm256_storeu_si256((__m256i*)tmp, count4); for (int j = 0; j < 16; j++) hist[j+48] += tmp[j]; }
额外优化建议
- 把
masks数组声明为static const并32字节对齐,这样编译器可以把它放到只读数据段,_mm256_load_si256加载时能利用对齐内存的带宽优势。 - 你使用的
_mm256_adds_epi16是饱和加法,适合统计次数不超过65535的场景。如果你的n非常大,可能需要换成普通加法(_mm256_add_epi16)并在最后处理溢出,或者用32位累加寄存器来避免饱和限制。 - 部分编译器也支持
_mm256_broadcastw_epi16,它和_mm256_set1_epi16功能一致,编译器会自动选择最优的指令实现,你可以根据习惯选择。
内容的提问来源于stack exchange,提问作者Dov
相关产品推荐
相关产品推荐

