如果我們希望在用SIMD來提升程序處理的性能,首先需要做的就是檢測程序所運行的平台是否支持相應的SIMD擴展。平台對SIMD擴展分為兩部分的支持:
- CPU對SIMD擴展的支持。SIMD擴展是隨着CPU的發展不斷改進的,CPU為SIMD擴展提供了硬件上的最基本支持。
- 操作系統對SIMD擴展的支持。目前PC上運行的基本都是多任務操作系統,也就是會“同時”運行着多個程序,這些程序依靠操作系統進行任務調度以使得多個程序看起來是在同時運行,在進行任務調度時需要進行程序上下文的切換,其中就包括寄存器內容的保存與恢復。操作系統對SIMD擴展的支持總的來說主要是在任務上下文切換時,對該SIMD擴展所使用的寄存器的保存與恢復。
CPU Identification
檢查平台對SIMD擴展的支持,必不可少的就是指令CPUID。CPUID即CPU Identification,通過該指令能獲取到CPU相關的各種信息,其中包括CPU制造商、CPU版本、CPU串號、對各種擴展的支持、Cache相關信息等等。
如果EFLAGS寄存器的ID flag(bit 21)能被置1或者置零的話,則表明能使用CPUID指令。CPUID沒有任何操作數,不過CPUID卻是一個功能性的指令,有輸入以及輸出。
- 輸入用的寄存器為EAX(有時也會用到ECX作為擴展輸入),用於指定CPUID的功能。在執行CPUID指令前需要往EAX寄存器寫入相應的值。
- 輸出用的寄存器為EAX、EBX、ECX、EDX共四個。在CPUID指令執行后可以從這四個寄存器中獲取到所需要的信息。
如下所示為CPUID的一些基本功能
|
Value |
|
||||||||||||||||||
|
|
|||||||||||||||||||
| 00H |
|
||||||||||||||||||
| 01H |
|
||||||||||||||||||
| 02H |
|
||||||||||||||||||
| 03H |
|
||||||||||||||||||
| ... | |||||||||||||||||||
檢查CPU對SIMD擴展的支持
通過指令CPUID可以檢測CPU對SIMD擴展的支持。在輸入為EAX=01H的情況下執行CPUID,可以使得ECX以及EDX返回如下信息:
其中與SIMD擴展硬件支持的相關bit如下,當相應的bit為1時表示支持該擴展:
| Register | Bit | Feature |
| ECX | 0 | SSE3 |
| 9 | SSSE3 | |
| 12 | FMA | |
| 19 | SSE4.1 | |
| 20 | SSE4.2 | |
| 25 | AES | |
| 28 | AVX | |
| EDX | 23 | MMX |
| 25 | SSE | |
| 26 | SSE2 |
在輸入為EAX=07H的情況下執行CPUID,可以通過返回的寄存器EBX上的bit5以及bit16分別檢測CPU對AVX2以及AVX512的支持狀況。
| Register | Bit | Feature |
| EBX | 5 | AVX2 |
| 16 | AVX512 |
檢查操作系統對SIMD擴展的支持
程序通過訪問寄存器XCR0(eXterned Control Register)可以得到操作系統對SIMD擴展的支持信息。該寄存器通過XSETBV進行設置,通過XGETBV進行讀取。
回顧上一小節,可以看到EAX=01H CPUID.ECX的26、27bit分別為XSAVE以及OSXSAVE。其中XSAVE為1則表示存在XCR0寄存器,並且可以通過XSETBV以及XGETBV訪問該寄存器。對操作系統來說,會先查看處理器是否支持XSAVE,如果支持,則會根據操作系統自身的實現情況去設置XCR0寄存器。不過,操作系統與一般的程序有不同的權限,操作系統可以通過設置CR4寄存器的bit8(CR4.OSXSAVE)來控制一般程序對XCR0的訪問權限,CPUID得到的OSXSAVE(bit27)表示的就是操作系統是否設置了允許一般程序去訪問XCR0寄存器,我們一般的程序只需要去判斷這一個bit就知道是否能訪問XCR0。
| Register | Bit | Feature | Description |
| ECX | 26 | XSAVE | 為1則表明該處理器支持XSAVE/XRSTOR,支持擴展state,支持XSETBV/XGETBV之類,支持XCR0寄存器 |
| 27 | OSXSAVE | 為1則表明操作系統允許一般程序通過XSETBV/SGETBV訪問XCR0寄存器,允許一般程序通過XSAVE/XRSTOR訪問擴展state |
如果OSXSAVE=1,則可以通過XGETBV指令訪問XCR0寄存器,得到系統對SIMD擴展的支持信息。指令XGETBV同樣也沒有任何的操作數,不過也存在輸入與輸出。其中輸入為ECX,用於指定XCR寄存器,一般只有XCR0,即ECX=0。輸出有64bit,保存於EDX:EAX。
XGETBV的輸出,即返回值的各個bit有如下指示:
| Register | Bit | Feature | Description |
| EAX | 0 | MMX/FPU | 這個bit必為1,表明操作系統支持MMX以及ST寄存器 |
| 1 | SSE | 為1則表明操作系統支持XMM寄存器,32位時為XMM0~XMM7,64位時為XMM0~XMM15 | |
| 2 | AVX | 為1則表明操作系統支持YMM寄存器,32位時為YMM0~YMM7,64位時為YMM0~YMM15 | |
| 6 | ZMM_Hi265 | 為1則表明操作系統支持ZMM寄存器,32位時為ZMM0~ZMM7,64位時為ZMM0~ZMM15 | |
| 7 | Hi16_ZMM | 為1則表明如果CPU工作在64位模式,則操作系統支持ZMM16~ZMM31 |
總結
查看平台對SIMD擴展的支持需要分別檢查處理器以及操作系統對SIMD擴展的支持。
- 處理器:調用EAX=1 CPUID,然后查看所返回的ECX或EDX寄存器相應的bit。
- 操作系統:調用EAX=1 CPUID,然后查看ECX.OSXSAVE(bit27),如果為1則調用XGETBV去獲取XCR0,最后查看返回值EAX上相應的bit。
GCC:
#include <stdio.h>
inline static void cpuid (unsigned int output[4], unsigned int EAX, unsigned int ECX) {
unsigned int a, b, c, d;
__asm("cpuid" : "=a"(a),"=b"(b),"=c"(c),"=d"(d) : "a"(EAX),"c"(ECX) : );
output[0] = a;
output[1] = b;
output[2] = c;
output[3] = d;
}
inline static unsigned int xgetbv (unsigned int ECX) {
unsigned int ret = 0;
__asm("xgetbv" : "=a"(ret) : "c"(ECX) : );
return ret;
}
int main(){
unsigned int CPUInfo[4] = {0}, ECX = 0, EDX = 0, XCR0_EAX = 0;
cpuid(CPUInfo, 1, 0);
ECX = CPUInfo[2];
EDX = CPUInfo[3];
if(EDX & 0x00800000)
printf("CPU Support MMX\n");
if(EDX & 0x02000000)
printf("CPU Support SSE\n");
if(EDX & 0x04000000)
printf("CPU Support SSE2\n");
if(ECX & 1)
printf("CPU Support SSE3\n");
if(ECX & 0x00000200)
printf("CPU Support SSSE3\n");
if(ECX & 0x00080000)
printf("CPU Support SSE4.1\n");
if(ECX & 0x00100000)
printf("CPU Support SSE4.2\n");
if(ECX & 0x02000000)
printf("CPU Support AES\n");
if(ECX & 0x10000000)
printf("CPU Support AVX\n");
if(ECX & 0x08000000)
printf("OS Support XSAVE\n");
else{
printf("OS not Support XSAVE, OS not Support SIMD\n");
return -1;
}
XCR0_EAX = xgetbv(0);
if(XCR0_EAX & 0x00000002)
printf("OS Support SSE/SSE2/SSE3/SSE4\n");
if(XCR0_EAX & 0x00000004)
printf("OS Support AVX\n");
if(XCR0_EAX & 0x00000040)
printf("OS Support AVX-512\n");
printf("ECX=%x, EDX=%x, XCR0_EAX=%x\n", ECX, EDX, XCR0_EAX);
return 0;
}
YASM:
global SIMD_Support
;this code just check SSE3 support
;bit0:SSE3, bit27:OSXSAVE
%define CPU_SUPPORT_CONST_ECX 0x08000001
;bit23:MMX, bit25:SSE, bit26:SSE2
%define CPU_SUPPORT_CONST_EDX 0x06800000
;bit2:XMM
%define OS_SUPPORT_CONST 2
SIMD_Support:
;processor supports
mov eax, 1
cpuid
and ecx, CPU_SUPPORT_CONST_ECX
cmp ecx, CPU_SUPPORT_CONST_ECX
jne not_supported
and edx, CPU_SUPPORT_CONST_EDX
cmp edx, CPU_SUPPORT_CONST_EDX
jne not_supported
;OS supports
mov ecx, 0
XGETBV; result in EDX:EAX
and eax, OS_SUPPORT_CONST
cmp eax, OS_SUPPORT_CONST
jne not_supported
mov eax, 0 ; return 0
ret
not_supported:
mov eax, -1 ; return -1
ret
Reference:
Intel 64 and IA-32 Architectures Software Developer's Manual



