zyl910

优化技巧、硬件体系、图像处理、图形学、游戏编程、国际化与文本信息处理。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

  以前我写了一篇《[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

 

源码下载——

https://files.cnblogs.com/zyl910/checksimd64.rar

posted on 2012-05-25 22:22  zyl910  阅读(4863)  评论(0编辑  收藏  举报