zoukankan      html  css  js  c++  java
  • [VC兼容32位和64位] 检查MMX和SSE系列指令集的支持级别

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

  • 相关阅读:
    python set
    python中%d %2d %02d %-2d% %.2d的区别
    python dict(字典)
    python 300本电子书合集
    python tuple元组
    python end用法
    python 找出第二大值
    GPU大百科全书 第二章 凝固生命的光栅化
    GPU大百科全书 第一章:美女 方程与几何
    Notepad++中调试用心lua程序
  • 原文地址:https://www.cnblogs.com/zyl910/p/checksimd64.html
Copyright © 2011-2022 走看看