为何简单浮点求和循环未自动向量化,性能远逊于SIMD intrinsic实现?
为何使用-O3 -march=native编译时,简单求和循环未被编译器向量化?
我发现编译器在使用-O3 -march=native选项编译时,没有对一个简单的求和循环使用SIMD指令,以下是两个对比函数:
float sum_simd(const std::vector<float>& vec) { __m256 a{0.0}; for (std::size_t i = 0; i < vec.size(); i += 8) { __m256 tmp = _mm256_loadu_ps(&vec[i]); a = _mm256_add_ps(tmp, a); } float res{0.0}; for (size_t i = 0; i < 8; ++i) { res += a[i]; } return res; } float normal_sum(const std::vector<float>& vec) { float sum{0}; for (size_t i = 0; i < vec.size(); ++i) { sum += vec[i]; } return sum; }
编译器对两个函数生成的核心汇编代码分别如下:
sum_simd的核心汇编指令:
vaddps ymm0, ymm0, ymmword ptr [rax + 4*rdx]
normal_sum的核心汇编指令:
vaddss xmm0, xmm0, dword ptr [rcx + 4*rsi] vaddss xmm0, xmm0, dword ptr [rcx + 4*rsi + 4] vaddss xmm0, xmm0, dword ptr [rcx + 4*rsi + 8] vaddss xmm0, xmm0, dword ptr [rcx + 4*rsi + 12] vaddss xmm0, xmm0, dword ptr [rcx + 4*rsi + 16] vaddss xmm0, xmm0, dword ptr [rcx + 4*rsi + 20] vaddss xmm0, xmm0, dword ptr [rcx + 4*rsi + 24] vaddss xmm0, xmm0, dword ptr [rcx + 4*rsi + 28]
本地机器及编译平台测试显示,手动SIMD实现的求和函数性能比普通版本高约10倍。我使用的是GCC 13和Clang 17编译器,编译选项为-O3 -march=native。
请问为何normal_sum函数性能更慢且未被完全向量化?是否需要添加额外编译选项?
原因分析
编译器默认不向量化这个求和循环,核心原因是浮点加法的关联性问题:
- 普通求和循环是将所有元素累加到单个
float变量中,严格遵循代码的计算顺序,每次加法结果都依赖前一次的输出。 - SIMD向量化会同时计算多组加法,最后再合并结果,这会改变浮点运算的中间顺序,导致最终结果可能和原代码存在微小精度差异。
- 编译器在默认优化等级下,优先保证计算结果的严格一致性,因此不会自动进行这种可能改变精度的向量化。
解决方法
如果可以接受浮点精度的微小差异(绝大多数场景下这种差异可忽略),可添加以下编译选项强制编译器进行向量化:
- GCC: 添加
-ffast-math选项,该选项放宽浮点运算的严格关联性要求,允许编译器进行向量化优化。 - Clang: 同样支持
-ffast-math,或更精细地使用-fassociative-math选项(仅允许关联性优化,不开启其他激进浮点优化)。
另外,也可通过代码层面调整引导编译器向量化:比如将求和拆分为多个累加器(如4个或8个),最后再合并结果,这样既保证计算顺序的灵活性,也能让编译器自动生成SIMD代码。
内容的提问来源于stack exchange,提问作者fabian
相关产品推荐
相关产品推荐

