为何_mm256_mul_epi32指令输出向量中隔位元素为零?
问题原因分析与解决方法
为什么会出现隔位零?
你遇到的问题核心是对_mm256_mul_epi32这个intrinsic的行为理解有误:
- 该函数对应AVX指令
VPMULDQ,它的作用是将256位向量视为4个64位元素(每个64位包含两个32位整数),仅对每个64位元素的低32位整数进行乘法,输出64位结果。 - 具体来说,它只会处理输入向量中索引为0、2、4、6的32位元素,每个乘积结果占64位(即输出向量中两个连续的32位位置)。因为你的乘积都未超过2^32,所以每个64位结果的高32位为0,这就是你看到隔位输出0的原因。
- 输入中索引为1、3、5、7的元素完全没有参与运算,自然不会出现在输出里。
正确实现向量点积的方法
如果要计算两个8元素32位整数向量的点积,需要根据是否需要处理溢出选择不同的实现方式:
方式1:忽略溢出(使用32位乘积)
用_mm256_mullo_epi32计算逐元素32位乘积,再对乘积向量做水平求和:
#include <iostream> #include <immintrin.h> #define ALIGN 64 using namespace std; int main() { int* a = (int*)aligned_alloc(ALIGN, sizeof(int)*8); int* b = (int*)aligned_alloc(ALIGN, sizeof(int)*8); a[0]=103; a[1]=198; a[2]=105; a[3]=115; a[4]=81; a[5]=255; a[6]=74; a[7]=236; b[0]=8; b[1]=172; b[2]=163; b[3]=32; b[4]=62; b[5]=247; b[6]=73; b[7]=132; __m256i vec_a = _mm256_load_si256((__m256i*)a); __m256i vec_b = _mm256_load_si256((__m256i*)b); // 计算逐元素32位乘积(溢出会被截断) __m256i mul_result = _mm256_mullo_epi32(vec_a, vec_b); // 拆分256位向量为两个128位向量,分别求和 __m128i low_half = _mm256_extracti128_si256(mul_result, 0); __m128i high_half = _mm256_extracti128_si256(mul_result, 1); // 128位向量内水平求和 low_half = _mm_add_epi32(low_half, _mm_shuffle_epi32(low_half, _MM_SHUFFLE(1,0,3,2))); low_half = _mm_add_epi32(low_half, _mm_shuffle_epi32(low_half, _MM_SHUFFLE(2,3,0,1))); int sum_low = _mm_cvtsi128_si32(low_half); high_half = _mm_add_epi32(high_half, _mm_shuffle_epi32(high_half, _MM_SHUFFLE(1,0,3,2))); high_half = _mm_add_epi32(high_half, _mm_shuffle_epi32(high_half, _MM_SHUFFLE(2,3,0,1))); int sum_high = _mm_cvtsi128_si32(high_half); int dot_product = sum_low + sum_high; cout << "点积结果:" << dot_product << endl; // 手动验证 int manual = 103*8 + 198*172 + 105*163 + 115*32 + 81*62 +255*247 +74*73 +236*132; cout << "手动计算结果:" << manual << endl; free(a); free(b); return 0; }
方式2:避免溢出(使用64位乘积)
需要分别处理奇偶索引元素的乘积,再合并求和:
#include <iostream> #include <immintrin.h> #define ALIGN 64 using namespace std; int main() { int* a = (int*)aligned_alloc(ALIGN, sizeof(int)*8); int* b = (int*)aligned_alloc(ALIGN, sizeof(int)*8); a[0]=103; a[1]=198; a[2]=105; a[3]=115; a[4]=81; a[5]=255; a[6]=74; a[7]=236; b[0]=8; b[1]=172; b[2]=163; b[3]=32; b[4]=62; b[5]=247; b[6]=73; b[7]=132; __m256i vec_a = _mm256_load_si256((__m256i*)a); __m256i vec_b = _mm256_load_si256((__m256i*)b); // 计算偶数索引元素的64位乘积 __m256i mul_even = _mm256_mul_epi32(vec_a, vec_b); // 将奇数索引元素移到偶数位置,以便用_mm256_mul_epi32计算 __m256i vec_a_odd = _mm256_permute4x64_epi64(vec_a, 0x55); // 交换每个64位lane内的两个32位元素 __m256i vec_b_odd = _mm256_permute4x64_epi64(vec_b, 0x55); // 计算奇数索引元素的64位乘积 __m256i mul_odd = _mm256_mul_epi32(vec_a_odd, vec_b_odd); // 合并两个乘积向量的64位元素 __m256i total_mul = _mm256_add_epi64(mul_even, mul_odd); // 水平求和64位元素 __m128i low_half = _mm256_extracti128_si256(total_mul, 0); __m128i high_half = _mm256_extracti128_si256(total_mul, 1); low_half = _mm_add_epi64(low_half, _mm_shuffle_epi32(low_half, _MM_SHUFFLE(1,0,3,2))); high_half = _mm_add_epi64(high_half, _mm_shuffle_epi32(high_half, _MM_SHUFFLE(1,0,3,2))); __m128i sum_total = _mm_add_epi64(low_half, high_half); long long dot_product = _mm_cvtsi128_si64(sum_total); cout << "64位点积结果:" << dot_product << endl; // 手动验证 long long manual = (long long)103*8 + (long long)198*172 + (long long)105*163 + (long long)115*32 + (long long)81*62 + (long long)255*247 + (long long)74*73 + (long long)236*132; cout << "手动计算结果:" << manual << endl; free(a); free(b); return 0; }
内容的提问来源于stack exchange,提问作者Abhishek Ghosh
相关产品推荐
相关产品推荐

