是的,CPUID + 检查那些 XCR0 位就足够了,假设操作系统没有损坏(并且遵循预期的约定)。
假设虚拟机或模拟器的 CPUID 指令没有说谎并告诉您 AVX2 可用但实际上是错误的。但如果发生上述任何一种情况,那是操作系统或虚拟机的问题,而不是您的程序的问题。
(为了兼容比较老的CPU,使用前需要先通过CPUID检查是否支持XGETBV,否则会报错。一个好的AVX检测功能就可以做到这一点。
另请参阅Which versions of Windows support/require which CPU multimedia extensions? (How to check if SSE or AVX are fully usable?) - 我的回答主要集中在后者,而不是特定于 Windows。)
如果您刚刚检查了 CPUID,您会发现 CPU 支持 AVX2,即使该 CPU 运行的是不了解 AVX 的旧操作系统,并且仅保存/恢复了 XMM 寄存器上下文切换,而不是 YMM。
英特尔设计了一些东西,因此在这种情况下,故障模式将是非法指令错误 (#UD),而不是在多个线程/进程使用 YMM 或 ZMM 寄存器时默默地破坏用户空间状态。 (因为那太可怕了。)
(每个任务都应该有自己的私有寄存器状态,包括整数和 FP/SIMD 寄存器。如果您查看程序,不保存/恢复 YMM 寄存器的高半部分的上下文切换将有效地异步破坏寄存器单线程的顺序执行。)
机制是操作系统必须在 XCR0(扩展控制寄存器 0)中设置一些位,用户空间可以通过 xgetbv 进行检查。如果设置了这些位,则有效地保证了操作系统支持 AVX 并将保存/恢复 YMM regs。并且它将设置其他控制寄存器位,因此 SSE 和 AVX 指令实际上可以正常工作。
我不确定这些位是否真的会影响 CPU 行为,或者它们是否仅作为内核向用户空间宣传 AVX 支持的一种通信机制存在。
(YMM 寄存器在 AVX1 中是新的,XMM 在 SSE1 中是新的。操作系统不需要知道 SSE4.x 或 AVX2,只需要知道如何保存新的架构状态。所以 AVX-512 是下一个 SIMD需要新操作系统支持的扩展。)
更新:我认为那些 XCR0 位实际上确实控制 AVX1/2 和 AVX-512 指令是否会 #UD。 MacOS 的 Darwin 内核显然只提供按需 AVX-512 支持,因此第一次使用 会 出错(但随后内核处理它并重新运行,我希望对用户空间透明)。见the source:
// darwin-xnu .../i386/fpu.c#L176
* On-demand AVX512 support
* ------------------------
* On machines with AVX512 support, by default, threads are created with
* AVX512 masked off in XCR0 and an AVX-sized savearea is used. However, AVX512
* capabilities are advertised in the commpage and via sysctl. If a thread
* opts to use AVX512 instructions, the first will result in a #UD exception.
* Faulting AVX512 intructions are recognizable by their unique prefix.
* This exception results in the thread being promoted to use an AVX512-sized
* savearea and for the AVX512 bit masks being set in its XCR0. The faulting
* instruction is re-driven and the thread can proceed to perform AVX512
* operations.
*
* In addition to AVX512 instructions causing promotion, the thread_set_state()
* primitive with an AVX512 state flavor result in promotion.
*
* AVX512 promotion of the first thread in a task causes the default xstate
* of the task to be promoted so that any subsequently created or subsequently
* DNA-faulted thread will have AVX512 xstate and it will not need to fault-in
* a promoted xstate.
*
* Two savearea zones are used: the default pool of AVX-sized (832 byte) areas
* and a second pool of larger AVX512-sized (2688 byte) areas.
*
* Note the initial state value is an AVX512 object but that the AVX initial
* value is a subset of it.
*/
所以在 MacOS 上,似乎 XGETBV + 检查 XCR0 可能不是是检测 AVX-512 指令可用性的保证方法!评论说“功能在 commpage 中和通过 sysctl 进行宣传”,因此您需要一些特定于操作系统的方式。
但那是 AVX-512;可能 AVX1/2 始终处于启用状态,因此检查 XCR0 是否适用于任何地方,包括 MacOS。
惰性上下文切换曾经是一件事
一些操作系统过去使用“惰性”上下文切换,在新进程实际使用它们之前,实际上并没有保存/恢复 x87、XMM 甚至 YMM 寄存器。这是通过使用单独的控制寄存器位来完成的,如果执行这些类型的指令就会出错;在该故障处理程序中,操作系统将保存该内核上前一个任务的状态,并从新任务加载状态。然后更改控制位并返回用户空间重新运行指令。
但是现在大多数进程在所有地方都使用 XMM(和 YMM)寄存器,在 memcpy 和其他 libc 函数中,以及用于复制/初始化结构。所以懒惰的策略是不值得的,而且会增加很多额外的复杂性,尤其是在 SMP 系统上。这就是现代内核不再这样做的原因。
内核用于使 x87、xmm 或 ymm 指令出错的控制寄存器位与我们正在检查的 XCR0 位是分开的,因此即使在使用惰性上下文切换的系统上,您的检测也不会被操作系统愚弄了,暂时设置了 CPU,所以vaddps xmm0, xmm1, xmm2 会出错。
当 SSE1 是新的时,在不使用特定于操作系统的 API 的情况下,没有用于检测 SSE 感知操作系统的用户空间可见位,但英特尔从 AVX 的错误中吸取了教训。 (但对于 SSE,故障模式仍然是故障,而不是损坏。CPU 启动时 SSE 指令设置为故障:How do I enable SSE for my freestanding bootable code?)