以前我写了一篇《[VC6] 检查MMX和SSE系列指令集的支持级别(最高SSE4.2)》(http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html)。现在发现该方法存在两点缺陷——
1.不支持64位,因为VC的64位程序不支持内嵌汇编;
2.没有区分硬件支持与操作系统支持。
怎么解决这两点缺陷呢?
对于第1点,可以利用Intrinsics函数来兼容32位和64位。为了更方便的使用CPUID指令,可以利用《如何在各个版本的VC及64位下使用CPUID指令》(http://www.cnblogs.com/zyl910/archive/2012/05/21/vcgetcpuid.html)的成果。
对于第2点,考虑到再增加一组函数会使函数过多,于是决定采用增加一个指针参数的方式——函数的返回值用于返回操作系统支持性,指针参数用于返回硬件支持性。因为操作系统支持性,对开发SIMD程序来说更加重要。
一、检查MMX
检查以下标志位可判断硬件是否支持MMX——
CPUID(1).EDX.MMX[bit 23]=1 // 硬件支持MMX
检查完硬件支持性,还需检查操作系统的支持性。最简单的办法就是使用结构化异常处理来执行一条MMX指令。代码如下——
BOOL simd_mmx(BOOL* phwmmx) { const INT32 BIT_D_MMX = 0x00800000; // bit 23 BOOL rt = FALSE; // result INT32 dwBuf[4]; // check processor support __cpuid(dwBuf, 1); // Function 1: Feature Information if ( dwBuf[3] & BIT_D_MMX ) rt=TRUE; if (NULL!=phwmmx) *phwmmx=rt; // check OS support if ( rt ) { #if defined(_WIN64) // VC编译器不支持64位下的MMX。 rt=FALSE; #else __try { _mm_empty(); // MMX instruction: emms } __except (EXCEPTION_EXECUTE_HANDLER) { rt=FALSE; } #endif // #if defined(_WIN64) } return rt; }
根据Intel文档,似乎64位下也支持MMX指令。但是VC编译器似乎不允许在64位下使用MMX指令(例如对于_mm_empty,会报告找不到符号链接)。具体原因尚不清楚。
二、检查SSE
检查以下标志位可判断硬件是否支持SSE——
CPUID(1).EDX.SSE[bit 25]=1 // 硬件支持SSE1
CPUID(1).EDX.SSE2[bit 26]=1 // 硬件支持SSE2
CPUID(1).ECX.SSE3[bit 0]=1 // 硬件支持SSE3
CPUID(1).ECX.SSSE3[bit 9]=1 // 硬件支持SSE3
CPUID(1).ECX.SSE41[bit 19]=1 // 硬件支持SSE4.1
CPUID(1).ECX.SSE42[bit 20]=1 // 硬件支持SSE4.2
检查完硬件支持性,还需检查操作系统的支持性。很多资料说需要检查以下标志位——
CR0.EM[bit 2]=0 // 浮点模拟被禁止
CR4.OSFXSR[bit 9]=1 // 在进程切换时,操作系统支持保护SIMD浮点状态
可是CR0、CR4这些控制寄存器只能在ring0中访问,而一般应用程序是ring3的,无法获得上述标志位信息。所以建议还是使用结构化异常处理来执行一条SSE指令。代码如下——
int simd_sse_level(int* phwsse) { const INT32 BIT_D_SSE = 0x02000000; // bit 25 const INT32 BIT_D_SSE2 = 0x04000000; // bit 26 const INT32 BIT_C_SSE3 = 0x00000001; // bit 0 const INT32 BIT_C_SSSE3 = 0x00000100; // bit 9 const INT32 BIT_C_SSE41 = 0x00080000; // bit 19 const INT32 BIT_C_SSE42 = 0x00100000; // bit 20 int rt = SIMD_SSE_NONE; // result INT32 dwBuf[4]; // check processor support __cpuid(dwBuf, 1); // Function 1: Feature Information if ( dwBuf[3] & BIT_D_SSE ) { rt = SIMD_SSE_1; if ( dwBuf[3] & BIT_D_SSE2 ) { rt = SIMD_SSE_2; if ( dwBuf[2] & BIT_C_SSE3 ) { rt = SIMD_SSE_3; if ( dwBuf[2] & BIT_C_SSSE3 ) { rt = SIMD_SSE_3S; if ( dwBuf[2] & BIT_C_SSE41 ) { rt = SIMD_SSE_41; if ( dwBuf[2] & BIT_C_SSE42 ) { rt = SIMD_SSE_42; } } } } } } if (NULL!=phwsse) *phwsse=rt; // check OS support __try { __m128 xmm1 = _mm_setzero_ps(); // SSE instruction: xorps if (0!=*(int*)&xmm1) rt = SIMD_SSE_NONE; // 避免Release模式编译优化时剔除上一条语句 } __except (EXCEPTION_EXECUTE_HANDLER) { rt = SIMD_SSE_NONE; } return rt; }
“if (0!=*(int*)&xmm1) rt = SIMD_SSE_NONE;”是为了避免编译优化剔除无意义语句。“*(int*)&xmm1”返回xmm1中的首个int,如果上面的“_mm_setzero_ps()”执行成功,那么它应该是0。
三、全部代码
全部代码——
#include <windows.h> #include <stdio.h> #include <conio.h> #include <tchar.h> #if _MSC_VER >=1400 // VC2005才支持intrin.h #include <intrin.h> // 所有Intrinsics函数 #else #include <emmintrin.h> // MMX, SSE, SSE2 #endif // SSE系列指令集的支持级别. simd_sse_level 函数的返回值。 #define SIMD_SSE_NONE 0 // 不支持 #define SIMD_SSE_1 1 // SSE #define SIMD_SSE_2 2 // SSE2 #define SIMD_SSE_3 3 // SSE3 #define SIMD_SSE_3S 4 // SSSE3 #define SIMD_SSE_41 5 // SSE4.1 #define SIMD_SSE_42 6 // SSE4.2 const char* simd_sse_names[] = { "None", "SSE", "SSE2", "SSE3", "SSSE3", "SSE4.1", "SSE4.2", }; char szBuf[64]; INT32 dwBuf[4]; #if defined(_WIN64) // 64位下不支持内联汇编. 应使用__cpuid、__cpuidex等Intrinsics函数。 #else #if _MSC_VER < 1600 // VS2010. 据说VC2008 SP1之后才支持__cpuidex void __cpuidex(INT32 CPUInfo[4], INT32 InfoType, INT32 ECXValue) { if (NULL==CPUInfo) return; _asm{ // load. 读取参数到寄存器 mov edi, CPUInfo; // 准备用edi寻址CPUInfo mov eax, InfoType; mov ecx, ECXValue; // CPUID cpuid; // save. 将寄存器保存到CPUInfo mov [edi], eax; mov [edi+4], ebx; mov [edi+8], ecx; mov [edi+12], edx; } } #endif // #if _MSC_VER < 1600 // VS2010. 据说VC2008 SP1之后才支持__cpuidex #if _MSC_VER < 1400 // VC2005才支持__cpuid void __cpuid(INT32 CPUInfo[4], INT32 InfoType) { __cpuidex(CPUInfo, InfoType, 0); } #endif // #if _MSC_VER < 1400 // VC2005才支持__cpuid #endif // #if defined(_WIN64) // 取得CPU厂商(Vendor) // // result: 成功时返回字符串的长度(一般为12)。失败时返回0。 // pvendor: 接收厂商信息的字符串缓冲区。至少为13字节。 int cpu_getvendor(char* pvendor) { INT32 dwBuf[4]; if (NULL==pvendor) return 0; // Function 0: Vendor-ID and Largest Standard Function __cpuid(dwBuf, 0); // save. 保存到pvendor *(INT32*)&pvendor[0] = dwBuf[1]; // ebx: 前四个字符 *(INT32*)&pvendor[4] = dwBuf[3]; // edx: 中间四个字符 *(INT32*)&pvendor[8] = dwBuf[2]; // ecx: 最后四个字符 pvendor[12] = '\0'; return 12; } // 取得CPU商标(Brand) // // result: 成功时返回字符串的长度(一般为48)。失败时返回0。 // pbrand: 接收商标信息的字符串缓冲区。至少为49字节。 int cpu_getbrand(char* pbrand) { INT32 dwBuf[4]; if (NULL==pbrand) return 0; // Function 0x80000000: Largest Extended Function Number __cpuid(dwBuf, 0x80000000); if (dwBuf[0] < 0x80000004) return 0; // Function 80000002h,80000003h,80000004h: Processor Brand String __cpuid((INT32*)&pbrand[0], 0x80000002); // 前16个字符 __cpuid((INT32*)&pbrand[16], 0x80000003); // 中间16个字符 __cpuid((INT32*)&pbrand[32], 0x80000004); // 最后16个字符 pbrand[48] = '\0'; return 48; } // 是否支持MMX指令集 BOOL simd_mmx(BOOL* phwmmx) { const INT32 BIT_D_MMX = 0x00800000; // bit 23 BOOL rt = FALSE; // result INT32 dwBuf[4]; // check processor support __cpuid(dwBuf, 1); // Function 1: Feature Information if ( dwBuf[3] & BIT_D_MMX ) rt=TRUE; if (NULL!=phwmmx) *phwmmx=rt; // check OS support if ( rt ) { #if defined(_WIN64) // VC编译器不支持64位下的MMX。 rt=FALSE; #else __try { _mm_empty(); // MMX instruction: emms } __except (EXCEPTION_EXECUTE_HANDLER) { rt=FALSE; } #endif // #if defined(_WIN64) } return rt; } // 检测SSE系列指令集的支持级别 int simd_sse_level(int* phwsse) { const INT32 BIT_D_SSE = 0x02000000; // bit 25 const INT32 BIT_D_SSE2 = 0x04000000; // bit 26 const INT32 BIT_C_SSE3 = 0x00000001; // bit 0 const INT32 BIT_C_SSSE3 = 0x00000100; // bit 9 const INT32 BIT_C_SSE41 = 0x00080000; // bit 19 const INT32 BIT_C_SSE42 = 0x00100000; // bit 20 int rt = SIMD_SSE_NONE; // result INT32 dwBuf[4]; // check processor support __cpuid(dwBuf, 1); // Function 1: Feature Information if ( dwBuf[3] & BIT_D_SSE ) { rt = SIMD_SSE_1; if ( dwBuf[3] & BIT_D_SSE2 ) { rt = SIMD_SSE_2; if ( dwBuf[2] & BIT_C_SSE3 ) { rt = SIMD_SSE_3; if ( dwBuf[2] & BIT_C_SSSE3 ) { rt = SIMD_SSE_3S; if ( dwBuf[2] & BIT_C_SSE41 ) { rt = SIMD_SSE_41; if ( dwBuf[2] & BIT_C_SSE42 ) { rt = SIMD_SSE_42; } } } } } } if (NULL!=phwsse) *phwsse=rt; // check OS support __try { __m128 xmm1 = _mm_setzero_ps(); // SSE instruction: xorps if (0!=*(int*)&xmm1) rt = SIMD_SSE_NONE; // 避免Release模式编译优化时剔除上一条语句 } __except (EXCEPTION_EXECUTE_HANDLER) { rt = SIMD_SSE_NONE; } return rt; } int _tmain(int argc, _TCHAR* argv[]) { //__cpuidex(dwBuf, 0,0); //__cpuid(dwBuf, 0); //printf("%.8X\t%.8X\t%.8X\t%.8X\n", dwBuf[0],dwBuf[1],dwBuf[2],dwBuf[3]); cpu_getvendor(szBuf); printf("CPU Vendor:\t%s\n", szBuf); cpu_getbrand(szBuf); printf("CPU Name:\t%s\n", szBuf); BOOL bhwmmx; // 硬件支持MMX BOOL bmmx; // 操作系统支持MMX bmmx = simd_mmx(&bhwmmx); printf("MMX: %d\t// hw: %d\n", bmmx, bhwmmx); int nhwsse; // 硬件支持SSE int nsse; // 操作系统支持SSE nsse = simd_sse_level(&nhwsse); printf("SSE: %d\t// hw: %d\n", nsse, nhwsse); for(int i=1; i<sizeof(simd_sse_names); ++i) { if (nhwsse>=i) printf("\t%s\n", simd_sse_names[i]); } return 0; }
在以下编译器中编译成功——
VC6(32位)
VC2003(32位)
VC2005(32位、64位)
VC2010(32位、64位)
四、测试结果
在64位的win7中执行“x64\Release\checksimd64_2010.exe”,运行效果——
还可以观察编译器生成的汇编代码,摘自“x64\Release\checksimd64.cod”——
; 187 : // check OS support ; 188 : __try ; 189 : { ; 190 : __m128 xmm1 = _mm_setzero_ps(); // SSE instruction: xorps 00077 0f 57 c0 xorps xmm0, xmm0 0007a 0f 29 44 24 10 movaps XMMWORD PTR xmm1$72829[rsp], xmm0 ; 191 : if (0!=*(int*)&xmm1) rt = SIMD_SSE_NONE; // 避免Release模式编译优化时剔除上一条语句 0007f 83 7c 24 10 00 cmp DWORD PTR xmm1$72829[rsp], 0 00084 45 0f 45 c2 cmovne r8d, r10d 00088 44 89 04 24 mov DWORD PTR rt$[rsp], r8d ; 192 : }
可见Release模式下也正常生成了xorps指令,并没有被编译优化掉。
参考文献——
《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z》. May 2012. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.html
《Intel® Processor Identification and the CPUID Instruction》. April 2012. http://developer.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html
《AMD64 Architecture Programmer's Manual Volume 3: General Purpose and System Instructions》. December 2011. http://support.amd.com/us/Processor_TechDocs/24594_APM_v3.pdf
《AMD CPUID Specification》. September 2010. http://support.amd.com/us/Embedded_TechDocs/25481.pdf
《[VC6] 检查MMX和SSE系列指令集的支持级别(最高SSE4.2)》:http://www.cnblogs.com/zyl910/archive/2012/03/01/checksimd.html
《如何在各个版本的VC及64位下使用CPUID指令》:http://www.cnblogs.com/zyl910/archive/2012/05/21/vcgetcpuid.html
源码下载——