运行时检测AVX2必须用__cpuid(0x00000007)查cpuInfo[1]第5位,不能依赖__AVX2__宏;需先调__cpuid(cpuInfo, 0)确认CPUID支持≥0x00000007,否则老CPU会触发#UD异常。

运行时检测必须用 __cpuid(0x00000007),不能只看 __AVX2__ 宏
编译器宏 __AVX2__ 是纯编译期开关,由 -mavx2 触发,它在代码里恒为真——哪怕你把它写进 if (__AVX2__) { },这个分支也永远不会被跳过。它不读 CPU,不查寄存器,只告诉编译器“允许生成 AVX2 指令”。结果就是:同一份二进制在 i5-2400(Sandy Bridge,无 AVX2)上直接 SIGILL。
__cpuid(0x00000007) 查 cpuInfo[1] 第 5 位才是关键
AVX2 支持位定义在 CPUID 叶子节点 0x00000007 的 EBX 寄存器(即返回数组的 cpuInfo[1])第 5 位。但必须先确认 CPUID 指令本身可用——调一次 __cpuid(cpuInfo, 0),检查 cpuInfo[0] 是否 ≥ 0x00000007,否则在极老 CPU(如 Pentium Pro)上调用会触发 #UD 异常。
- Windows / MSVC:
int cpuInfo[4]; __cpuid(cpuInfo, 0x00000007); bool has_avx2 = (cpuInfo[1] & (1 - Linux / GCC/Clang:
unsigned int eax, ebx, ecx, edx; __get_cpuid(0x00000007, &eax, &ebx, &ecx, &edx); bool has_avx2 = (ebx & (1U - 别漏掉前置检查:
__cpuid(cpuInfo, 0)后再查0x00000007,否则未定义行为
AVX2 ≠ AVX,OS 状态不影响 AVX2 执行,但影响 AVX
AVX2 不依赖 XSAVE/XRSTOR 状态,只要 CPU 硬件支持(0x00000007 bit 5 置位),就能安全执行 _mm256_add_epi32 等指令。这点和 AVX 不同:AVX 要求 OS 已启用扩展状态管理(xgetbv(0) 低两位为 0b11),否则会 #UD;而 AVX2 是 AVX 的超集,其指令不引入新的上下文保存需求。
- 所以检测 AVX2 时,不用调
xgetbv,也不用查__cpuid(0x00000001)的 ECX bit 28 - 但如果你同时提供 AVX 和 AVX2 分支,仍需确保 AVX 分支自己做 OS 检查,避免混用出错
- AVX-512 则更复杂:要查多个叶子节点(
0x00000007,0x0000001f),且子集(F/CD/ER)需单独判断
更省事的选择:__builtin_cpu_supports("avx2")
GCC ≥ 4.9 / Clang ≥ 3.5 提供了内建函数 __builtin_cpu_supports,它底层仍是 cpuid,但封装了所有细节,无需手动位运算、叶子节点校验或跨平台适配:
立即学习“C++免费学习笔记(深入)”;
- 字符串必须是字面量:
if (__builtin_cpu_supports("avx2")) { ... }✅;const char* s = "avx2"; if (__builtin_cpu_supports(s))❌(编译报错) - 即使没加
-mavx2,该函数仍可调用,不会生成非法指令 - 不支持模糊别名:
"avx512"不可靠,得用"avx512f"或"avx512cd"
真正容易被忽略的是:AVX2 指令在频繁系统调用场景下,会放大上下文切换开销——因为 YMM 寄存器比 XMM 多一倍宽度,保存/恢复成本更高。如果你的 kernel 每毫秒 syscall 几十次,AVX2 加速可能被抵消。别只盯着峰值吞吐,得看实际调度路径。


















