GCC 12.3生成条件执行SIMD代码的原因及优化方案咨询
关于GCC版本对SIMD代码生成的疑问
当用-O3编译以下C++代码时,GCC 12.3会生成带条件分支的SIMD汇编;GCC 13.2则始终生成SIMD代码,而Clang 17.0.1完全不生成SIMD代码。
#include <array> __attribute__((noinline)) void fn(std::array<int, 4>& lhs, const std::array<int, 4>& rhs) { for (std::size_t idx = 0; idx != 4; ++idx) { lhs[idx] = lhs[idx] + rhs[idx]; } }
以下是GCC 12.3(带-O3参数)生成的实际汇编代码:
fn(std::array<int, 4ul>&, std::array<int, 4ul> const&): lea rdx, [rsi+4] mov rax, rdi sub rax, rdx cmp rax, 8 jbe .L2 movdqu xmm0, XMMWORD PTR [rsi] movdqu xmm1, XMMWORD PTR [rdi] paddd xmm0, xmm1 movups XMMWORD PTR [rdi], xmm0 ret .L2: mov eax, DWORD PTR [rsi] add DWORD PTR [rdi], eax mov eax, DWORD PTR [rsi+4] add DWORD PTR [rdi+4], eax mov eax, DWORD PTR [rsi+8] add DWORD PTR [rdi+8], eax mov eax, DWORD PTR [rsi+12] add DWORD PTR [rdi+12], eax ret
a) 前5条汇编指令的作用
lea rdx, [rsi+4]:计算rhs数组起始地址加4后的地址,存入rdx(rsi对应函数第二个参数const std::array<int,4>& rhs的指针)。mov rax, rdi:将lhs数组的指针(rdi对应函数第一个参数)复制到rax。sub rax, rdx:计算lhs地址与rhs+4地址的差值,结果存入rax。cmp rax, 8:将上述差值与8做比较,判断两个数组的内存位置是否满足SIMD操作的安全条件。jbe .L2:如果差值小于等于8(即两个数组内存区域距离过近,可能存在重叠),跳转到.L2执行标量版本的加法;否则执行SIMD批量加法。
本质上这5条指令是检查数组是否存在内存重叠:若内存重叠,SIMD的批量读写会导致数据覆盖错误,因此改用标量逐个操作;反之则用SIMD提升执行效率。
b) 让GCC 12.3生成纯SIMD代码的方法
无需手动编写SSE指令,可通过以下方式实现:
- 添加
__restrict关键字:明确告知编译器两个数组指针不会指向重叠内存,消除编译器对内存安全的顾虑,直接生成SIMD代码:__attribute__((noinline)) void fn(std::array<int, 4>& __restrict lhs, const std::array<int, 4>& __restrict rhs) - 使用编译选项
-fno-tree-loop-distribute-patterns:禁用GCC的循环分发模式优化,不过该选项可能影响其他代码的优化效果,不如__restrict精准。 - 显式指定CPU SIMD支持:比如添加
-msse4.1或-mavx(根据CPU实际支持选择),不过GCC 12.3在-O3下默认已启用基础SSE支持,此方法优先级低于前两种。
内容的提问来源于stack exchange,提问作者MarkB
相关产品推荐
相关产品推荐

