x86 Intrinsic优化:非交错复数浮点向量乘法的Load/Store优化咨询
优化非交错复数浮点向量乘法的SIMD实现
先纠正公式笔误
首先注意到你给出的复数乘法公式存在笔误,正确的逐元素复数乘法逻辑应为:
Cre[i] = Are[i] * Bre[i] - Aim[i] * Bim[i]; Cim[i] = Are[i] * Bim[i] + Aim[i] * Bre[i];
以下优化方案基于该正确公式展开。
核心优化方向
针对非交错存储导致的Load/Store操作冗余问题,可从以下维度优化:
- 循环展开+指令级并行:一次处理多个复数元素,复用已加载的SIMD向量,减少循环控制开销,让CPU的内存访问单元与计算单元并行工作,隐藏内存延迟。
- 使用宽位SIMD寄存器:采用256位AVX/AVX2或512位AVX-512指令集,单次加载更多元素,直接降低单位元素的Load/Store次数。
- 可选:数据格式转换:若后续存在多次复数运算,提前将非交错格式转为交错格式(实部与虚部连续存储),可进一步压缩内存访问次数。
具体实现示例(AVX2版本)
以下代码基于AVX2指令集,单次处理4个复数元素,相比SSE减少一半的Load/Store操作:
#include <immintrin.h> void complex_multiply_avx2(const float* Are, const float* Aim, const float* Bre, const float* Bim, float* Cre, float* Cim, size_t N) { size_t i = 0; // 批量处理可被4整除的元素(每个AVX2向量承载4个复数的实/虚部) for (; i <= N - 4; i += 4) { // 单次加载4组实部、虚部 __m256 are = _mm256_load_ps(Are + i); __m256 aim = _mm256_load_ps(Aim + i); __m256 bre = _mm256_load_ps(Bre + i); __m256 bim = _mm256_load_ps(Bim + i); // 并行计算实部与虚部 __m256 cre = _mm256_sub_ps(_mm256_mul_ps(are, bre), _mm256_mul_ps(aim, bim)); __m256 cim = _mm256_add_ps(_mm256_mul_ps(are, bim), _mm256_mul_ps(aim, bre)); // 批量存储结果 _mm256_store_ps(Cre + i, cre); _mm256_store_ps(Cim + i, cim); } // 处理剩余不足4个的元素(用标量或SSE指令兼容) for (; i < N; ++i) { Cre[i] = Are[i] * Bre[i] - Aim[i] * Bim[i]; Cim[i] = Are[i] * Bim[i] + Aim[i] * Bre[i]; } }
进阶优化:转交错格式复用(可选)
若后续有多次复数运算,建议提前将非交错格式转为交错格式(每个复数的实部、虚部连续存储),进一步减少内存访问次数:
// 非交错格式转交错格式 void interleave_complex(const float* real, const float* imag, float* interleaved, size_t N) { size_t i = 0; for (; i <= N - 4; i += 4) { __m256 r = _mm256_load_ps(real + i); __m256 i_ = _mm256_load_ps(imag + i); // 交错排列:r0,i0,r1,i1,r2,i2,r3,i3 __m256 interleaved_low = _mm256_unpacklo_ps(r, i_); __m256 interleaved_high = _mm256_unpackhi_ps(r, i_); _mm256_store_ps(interleaved + i*2, interleaved_low); _mm256_store_ps(interleaved + i*2 + 8, interleaved_high); } // 处理剩余元素 for (; i < N; ++i) { interleaved[i*2] = real[i]; interleaved[i*2+1] = imag[i]; } } // 交错格式下的复数乘法 void complex_multiply_interleaved_avx2(const float* A, const float* B, float* C, size_t N) { size_t i = 0; for (; i <= N - 4; i += 4) { __m256 a = _mm256_load_ps(A + i*2); __m256 b = _mm256_load_ps(B + i*2); // 提取实部与虚部 __m256 ar = _mm256_shuffle_ps(a, a, _MM_SHUFFLE(2,0,2,0)); __m256 ai = _mm256_shuffle_ps(a, a, _MM_SHUFFLE(3,1,3,1)); __m256 br = _mm256_shuffle_ps(b, b, _MM_SHUFFLE(2,0,2,0)); __m256 bi = _mm256_shuffle_ps(b, b, _MM_SHUFFLE(3,1,3,1)); // 并行计算 __m256 cr = _mm256_sub_ps(_mm256_mul_ps(ar, br), _mm256_mul_ps(ai, bi)); __m256 ci = _mm256_add_ps(_mm256_mul_ps(ar, bi), _mm256_mul_ps(ai, br)); // 交错回存结果 __m256 c_low = _mm256_unpacklo_ps(cr, ci); __m256 c_high = _mm256_unpackhi_ps(cr, ci); _mm256_store_ps(C + i*2, c_low); _mm256_store_ps(C + i*2 + 8, c_high); } // 处理剩余元素 for (; i < N; ++i) { float ar = A[i*2], ai = A[i*2+1]; float br = B[i*2], bi = B[i*2+1]; C[i*2] = ar*br - ai*bi; C[i*2+1] = ar*bi + ai*br; } }
额外优化建议
- 内存对齐:确保所有数组按SIMD寄存器大小对齐(如AVX2需32字节对齐),使用
_mm_malloc或编译器对齐属性(如__attribute__((aligned(32)))),避免非对齐访问的性能损耗。 - 编译器优化:开启最高优化级别(如GCC
-O3 -mavx2、MSVC/O2 /arch:AVX2),编译器会自动完成指令调度与冗余消除。 - 预取指令:若内存带宽紧张,使用
_mm_prefetch提前加载后续数据,隐藏内存访问延迟。
内容的提问来源于stack exchange,提问作者Zvi Vered
相关产品推荐
相关产品推荐

