CPU 特性探测与优化(CPUID & Optimization)
CPUID 指令
cpuid 根据 %eax(有时 %ecx)的功能号返回处理器信息。结果在 %eax %ebx %ecx %edx。常用功能号:
| 输入 EAX | 返回内容 |
|---|---|
0 |
厂商 ID(ebx:edx:ecx = "GenuineIntel" 等)及最大功能号 |
1 |
特性位(EDX/ECX 中的 SSE、AVX、FPU 等标志) |
7 |
扩展特性(EBX 中的 AVX2、BMI、AVX-512 等,需 ECX=0) |
0x80000001 |
扩展特性(64 位、ABM 等) |
检测 SSE/AVX 是否可用
# 检测 AVX(CPUID leaf 1,ECX bit 28)
movl $1, %eax
cpuid
btl $28, %ecx # AVX 支持?
jc has_avx
# 检测 AVX2(leaf 7, ebx bit 5)
movl $7, %eax
xorl %ecx, %ecx
cpuid
btl $5, %ebx
jc has_avx2
注意:使用 AVX 前还需检查 OS 是否启用 YMM 状态保存(XCR0 中 SSE/AVX 位),否则执行 AVX 指令会 #UD:
# 检查 XCR0(用 xgetbv 读扩展控制寄存器)
xorl %ecx, %ecx
xgetbv # edx:eax = XCR0
andl $6, %eax # bit1 = SSE 状态, bit2 = AVX 状态
cmpl $6, %eax
jne no_avx # OS 未启用
实际库函数(如 glibc 的 CPU 特性检测)会完整检测,一般不需要手写。
关键特性位(leaf 1)
| 位 | 标志 | 含义 |
|---|---|---|
| EDX 23 | MMX | MMX 指令集 |
| EDX 25 | SSE | SSE |
| EDX 26 | SSE2 | SSE2 |
| ECX 0 | SSE3 | SSE3 |
| ECX 9 | SSSE3 | 补充 SSE3 |
| ECX 19 | SSE4.1 | SSE4.1 |
| ECX 20 | SSE4.2 | SSE4.2 |
| ECX 28 | AVX | AVX |
| ECX 12 | FMA | fused multiply-add |
性能优化要点
- 减少内存访问:把热数据放寄存器;利用
%r8–%r15等额外寄存器避免频繁 load/store。 - 指令级并行:现代 CPU 乱序执行,独立指令链越多吞吐越高;避免长依赖链(如循环累加可展开成 4 路)。
- 避免流水线停顿:
div/idiv延迟高(~20-100 周期),用乘法+shift 替代或预计算倒数。- 分支预测失败代价高,用条件传送
cmov或无分支技巧。
- 对齐热循环与数据:代码 16/32 字节对齐(
-falign-loops),SIMD 数据 16/32 字节对齐。 - SIMD 化:能用
addps/vaddps一次处理 4/8 个元素就别用标量循环。 - 栈对齐:保持 16 字节对齐,避免
movaps在栈上触发异常。 rep movs/rep stos:大块拷贝/清零交给 CPU 微码最优化。- 宏融合(macro-fusion):某些
cmp/test+ 相邻条件跳转可被融为单微操作,注意把比较紧挨跳转。 - 避免部分寄存器停顿:写 8/16 位子寄存器后立即读整个 64 位寄存器会产生停顿,用
movzbl/movzwl零扩展替代直接写%al/%ax。 - 零扩展优先于 32 位拷贝:需要清零整个寄存器时,
xorl %eax, %eax比movl $0, %eax短,两者都比movq $0, %rax好。
循环展开示例
# 普通累加 4 个元素(未展开)
xorq %rax, %rax
movq $0, %rcx
1: addq (%rdi,%rcx,8), %rax
incq %rcx
cmpq %rdx, %rcx
jl 1b
# 4 路展开(减少分支与循环开销;假定 n 是 4 的倍数,余数需单独循环处理)
xorq %rax, %rax
xorq %rcx, %rcx
shrq $2, %rdx # 次数/4
1: addq (%rdi,%rcx,8), %rax
addq 8(%rdi,%rcx,8), %rax
addq 16(%rdi,%rcx,8), %rax
addq 24(%rdi,%rcx,8), %rax
addq $4, %rcx
cmpq %rdx, %rcx
jl 1b
无分支绝对值(avoid branch)
# rax = |rdi|
movq %rdi, %rax
movq %rdi, %rcx
sarq $63, %rcx # rcx = 符号位掩码(全0或全1)
xorq %rcx, %rax
subq %rcx, %rax # rax = (rdi ^ mask) - mask
读取时间戳(性能计时)
rdtsc # EDX:EAX = 64 位 TSC 计数(需序列化避免乱序)
lfence # 常配合 rdtsc/rdtscp 隔离
rdtscp # 含处理器 ID,且序列化