• [C++] 测试硬件popcnt(位1计数)指令与各种软件算法,利用模板实现静态多态优化性能


    一、popcnt指令简介

      popcnt是“population count”的缩写,该操作一般翻译为“位1计数”,即统计有多少个“为1的位”。例如,十六进制数“FF”,它有8个为1的位,即“popcnt(0xFF) = 8”。popcnt主要应用在密码学与通信安全,例如计算汉明重量(Hamming weight)。

      x86体系最初是没有硬件popcnt指令的,只能靠软件计算。
      2008年底,Intel发布了Nehalem架构的处理器,增加了SSE4.2指令集,其中就有硬件popcnt指令。虽然它名义上是属于SSE4.2指令集,但它并不使用XMM寄存器(SSE的128位寄存器),而是使用GPR寄存器(General-Purpose Registers,通用寄存器)。甚至它的CPUID标志位也不是SSE4.2(CPUID.01H:ECX.SSE4_2[bit 20]),而是专门的POPCNT标志位(CPUID.01H:ECX.POPCNT[bit 23])。

      使用我以前写的三个模块,可以很方便的解决跨平台问题和指令检查问题——
    stdint:智能支持C99的stdint.h,解决整数类型问题。最新版的地址是 http://www.cnblogs.com/zyl910/archive/2012/08/08/c99int.html
    zintrin:在编译时检测Intrinsic函数集支持性,并自动引入相关头文件、修正细节问题。最新版的地址是 http://www.cnblogs.com/zyl910/archive/2012/10/01/zintrin_v101.html
    ccpuid:在编译时检测指令集的支持性。最新版的地址是 http://www.cnblogs.com/zyl910/archive/2012/10/13/ccpuid_v103.html

      具体来说,检查popcnt指令是这样做的——
    INTRIN_POPCNT 宏是 zintrin.h 提供的,可用来在编译时检测编译器是否支持popcnt指令集。
    getcpuidfield(CPUF_POPCNT) 是 ccpuid.h 提供的,可用来在运行时检测当前系统环境是否支持popcnt指令集。


    二、编程思路

      为了测试硬件popcnt的性能,我找了几个软件算法跟它进行比较。一个是最基本逐位判断算法,一个是查表法,另外还使用了《高效程序的奥秘》上的高级算法。

      为了比较这些算法的性能,可以让它们去统计一个数组中有多少个为1的位。数据样本足够大,才好分析平均性能。

      但是现在有一个问题,怎样去编写数组统计函数呢。
      首先想到的是,为每一种算法编写一套数组统计函数。优点是适合编译器优化,运行效率高。缺点是代码量大,复杂性高、重用性差。

      于是我想,如果能将各种popcnt算法 与 数组统计函数 分离就好了。

      具体怎么分离呢?

      在C语言中,有2种办法——
    1. 函数指针。先约定popcnt的函数参数,定义一个指针类型,然后各种popcnt算法根据该约定编写好函数。而数组统计函数接收一个popcnt函数指针参数,循环调用该函数指针进行统计。
    2. 宏。将数组统计写成宏,接收一个函数名参数,然后根据该函数名写循环进行统计。

      这两种方式都不太合适。函数指针难以内联优化,函数指针调用会带来一定的开销,影响性能。而用宏的话,没有语法检查,难以编写与调试,可读性差不易维护。

      于是将目光转向C++,首先想到的是使用虚函数——先定义一个popcnt虚基类,定义好接口。然后各种popcnt算法的类继承该接口,实现算法。而数组统计函数接收该类的实例,写循环进行统计。
      但这也存在性能问题,虚函数无法内联优化。而且popcnt是纯算法,不应该使用“创建实例再调用”的方式,最好是设计成类中静态函数,可直接调用。

      虚函数是动态多态,C++中有没有静态多态的语法呢?有,函数重载、模板。
      因popcnt是纯算法函数,函数参数格式应该是一样,但函数重载要求函数参数不同。
      而模板正是我们所需要的。编译优化时会尽量展开模板,进行内联优化。

      大致思路如下——
    1.将各种popcnt算法作为不同的类,类中只有静态函数,这些类的静态函数的参数格式均相同。
    2.将数组统计函数写成函数模板,模板参数用于传递popcnt算法类,然后在循环中使用模板参数调用它的静态函数。

      而且还可以进一步扩展,构造出两路循环展开版数组统计函数、四路循环展开版数组统计函数。然后测试程序也可以利用模板传递类型来简化。

      该方法的优点是能充分利用编译优化来提高性能,代码量少、结构清晰、能很方便的重用。
      缺点是,因C++不支持模板参数约束,存在误用风险,而且IDE无法提供成员函数提示。

      VC6对C++标准支持性较差,不支持将模板函数转为函数指针,导致无法使用该方法。直到VC2003,才能通过编译。


    三、全部代码

    3.1 testpopcnt.cpp

      全部代码——

    #define __STDC_LIMIT_MACROS    1    // C99整数范围常量. [纯C程序可以不用, 而C++程序必须定义该宏.]
    #define __STDC_CONSTANT_MACROS    1    // C99整数常量宏. [纯C程序可以不用, 而C++程序必须定义该宏.]
    
    #include <stdlib.h>
    #include <stdio.h>
    #include <time.h>
    
    #include "zintrin.h"
    #include "ccpuid.h"
    
    #if !defined(UINT32_C)
    #error Need C99 marcos: __STDC_CONSTANT_MACROS.
    #endif
    
    // Compiler name
    #define MACTOSTR(x)    #x
    #define MACROVALUESTR(x)    MACTOSTR(x)
    #if defined(__ICL)    // Intel C++
    #  if defined(__VERSION__)
    #    define COMPILER_NAME    "Intel C++ " __VERSION__
    #  elif defined(__INTEL_COMPILER_BUILD_DATE)
    #    define COMPILER_NAME    "Intel C++ (" MACROVALUESTR(__INTEL_COMPILER_BUILD_DATE) ")"
    #  else
    #    define COMPILER_NAME    "Intel C++"
    #  endif    // #  if defined(__VERSION__)
    #elif defined(_MSC_VER)    // Microsoft VC++
    #  if defined(_MSC_FULL_VER)
    #    define COMPILER_NAME    "Microsoft VC++ (" MACROVALUESTR(_MSC_FULL_VER) ")"
    #  elif defined(_MSC_VER)
    #    define COMPILER_NAME    "Microsoft VC++ (" MACROVALUESTR(_MSC_VER) ")"
    #  else
    #    define COMPILER_NAME    "Microsoft VC++"
    #  endif    // #  if defined(_MSC_FULL_VER)
    #elif defined(__GNUC__)    // Microsoft VC++
    #  if defined(__CYGWIN__)
    #    define COMPILER_NAME    "GCC(Cygmin) " __VERSION__
    #  elif defined(__MINGW32__)
    #    define COMPILER_NAME    "GCC(MinGW) " __VERSION__
    #  else
    #    define COMPILER_NAME    "GCC " __VERSION__
    #  endif    // #  if defined(_MSC_FULL_VER)
    #else
    #  define COMPILER_NAME    "Unknown Compiler"
    #endif    // #if defined(__ICL)    // Intel C++
    
    
    //////////////////////////////////////////////////
    // MPopcnt
    //////////////////////////////////////////////////
    
    // 位1计数. 最基本算法, 循环右移判断最低位是不是1.
    class MPopcnt_Base
    {
    public:
        // 位1计数(8位版).
        inline static size_t popcnt(uint8_t v)
        {
            size_t rt = 0;
            for(int i=0; i<8; ++i)
            {
                rt += (v & 1);
                v >>= 1;
            }
            return rt;
        }
    
        // 位1计数(32位版).
        inline static size_t popcnt(uint32_t v)
        {
            size_t rt = 0;
            for(int i=0; i<32; ++i)
            {
                rt += (v & 1);
                v >>= 1;
            }
            return rt;
        }
    
        // 位1计数(64位版).
        inline static size_t popcnt(uint64_t v)
        {
            size_t rt = 0;
            for(int i=0; i<64; ++i)
            {
                rt += ((size_t)v & 1);
                v >>= 1;
            }
            return rt;
        }
    };
    
    // 位1计数. 使用X86的POPCNT指令.
    #ifdef INTRIN_POPCNT
    class MPopcnt_Mx86
    {
    public:
        // 位1计数(8位版).
        inline static size_t popcnt(uint8_t v)
        {
            size_t rt;
    #if INTRIN_WORDSIZE>=64
            rt = popcnt((uint64_t)v);
    #else
            rt = popcnt((uint32_t)v);
    #endif
            return rt;
        }
    
        // 位1计数(32位版).
        inline static size_t popcnt(uint32_t v)
        {
            return (size_t)_mm_popcnt_u32(v);
        }
    
        // 位1计数(64位版).
        inline static size_t popcnt(uint64_t v)
        {
            size_t rt;
    #if INTRIN_WORDSIZE>=64
            rt = (size_t)_mm_popcnt_u64(v);
    #else
            rt = (size_t)(_mm_popcnt_u32((uint32_t)v) + _mm_popcnt_u32((uint32_t)(v>>32)));
    #endif
            return rt;
        }
    };
    #endif    // #ifdef INTRIN_POPCNT
    
    // 位1计数. 查表法.
    class MPopcnt_Table
    {
    public:
        // 位1计数(8位版).
        inline static size_t popcnt(uint8_t v)
        {
            static const size_t countTable[256] ={
                0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
                1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
                1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
                2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
                1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
                2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
                2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
                3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
                1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
                2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
                2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
                3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
                2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
                3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
                3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
                4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
            return countTable[v];
        }
    
        // 位1计数(32位版).
        inline static size_t popcnt(uint32_t v)
        {
            return popcnt((uint8_t)v) + popcnt((uint8_t)(v>>8)) + popcnt((uint8_t)(v>>16)) + popcnt((uint8_t)(v>>24));
        }
    
        // 位1计数(64位版).
        inline static size_t popcnt(uint64_t v)
        {
            return popcnt((uint8_t)v) + popcnt((uint8_t)(v>>8)) + popcnt((uint8_t)(v>>16)) + popcnt((uint8_t)(v>>24)) +  popcnt((uint8_t)(v>>32)) + popcnt((uint8_t)(v>>40)) + popcnt((uint8_t)(v>>48)) + popcnt((uint8_t)(v>>56));
        }
    };
    
    
    // 位1计数. 《Hacker's Delight》的分治法基本算法.
    class MPopcnt_HakBase
    {
    public:
        // 位1计数(8位版).
        inline static size_t popcnt(uint8_t v)
        {
            v = (v & 0x55) + ( (v >> 1) & 0x55);
            v = (v & 0x33) + ( (v >> 2) & 0x33);
            v = (v & 0x0f) + ( (v >> 4) & 0x0f);
            return (size_t)v;
        }
    
        // 位1计数(32位版).
        inline static size_t popcnt(uint32_t v)
        {
            v = (v & UINT32_C(0x55555555)) + ( (v >> 1) & UINT32_C(0x55555555));
            v = (v & UINT32_C(0x33333333)) + ( (v >> 2) & UINT32_C(0x33333333));
            v = (v & UINT32_C(0x0f0f0f0f)) + ( (v >> 4) & UINT32_C(0x0f0f0f0f));
            v = (v & UINT32_C(0x00ff00ff)) + ( (v >> 8) & UINT32_C(0x00ff00ff));
            v = (v & UINT32_C(0x0000ffff)) + ( (v >>16) & UINT32_C(0x0000ffff));
            return (size_t)v;
        }
    
        // 位1计数(64位版).
        inline static size_t popcnt(uint64_t v)
        {
            v = (v & UINT64_C(0x5555555555555555)) + ( (v >> 1) & UINT64_C(0x5555555555555555));
            v = (v & UINT64_C(0x3333333333333333)) + ( (v >> 2) & UINT64_C(0x3333333333333333));
            v = (v & UINT64_C(0x0f0f0f0f0f0f0f0f)) + ( (v >> 4) & UINT64_C(0x0f0f0f0f0f0f0f0f));
            v = (v & UINT64_C(0x00ff00ff00ff00ff)) + ( (v >> 8) & UINT64_C(0x00ff00ff00ff00ff));
            v = (v & UINT64_C(0x0000ffff0000ffff)) + ( (v >>16) & UINT64_C(0x0000ffff0000ffff));
            v = (v & UINT64_C(0x00000000ffffffff)) + ( (v >>32) & UINT64_C(0x00000000ffffffff));
            return (size_t)v;
        }
    };
    
    // 位1计数. 《Hacker's Delight》的分治法的改进算法.
    class MPopcnt_HakBaseFast
    {
    public:
        // 位1计数(8位版).
        inline static size_t popcnt(uint8_t v)
        {
            return MPopcnt_HakBase::popcnt(v);
        }
    
        // 位1计数(32位版).
        inline static size_t popcnt(uint32_t v)
        {
            v = v - ( (v >> 1) & UINT32_C(0x55555555));
            v = (v & UINT32_C(0x33333333)) + ( (v >> 2) & UINT32_C(0x33333333));
            v = ( v + (v >> 4) ) & UINT32_C(0x0f0f0f0f);
            v = v + (v >> 8);
            v = v + (v >>16);
            return (size_t)(v&0x3f);
        }
    
        // 位1计数(64位版).
        inline static size_t popcnt(uint64_t v)
        {
            v = v - ( (v >> 1) & UINT64_C(0x5555555555555555));
            v = (v & UINT64_C(0x3333333333333333)) + ( (v >> 2) & UINT64_C(0x3333333333333333));
            v = ( v + (v >> 4) ) & UINT64_C(0x0f0f0f0f0f0f0f0f);
            v = v + (v >> 8);
            v = v + (v >>16);
            v = v + (v >>32);
            return (size_t)(v&0x7f);
        }
    };
    
    
    
    // 将最常用的版本定义一个短名称.
    typedef MPopcnt_HakBase MPopcnt;
    
    
    //////////////////////////////////////////////////
    // mpopcnt_array
    //////////////////////////////////////////////////
    
    // 数组的位1计数_内部函数.
    template<class TPopcnt, class TUINT>
    inline size_t mpopcnt_array_internal(const void* pbuf, size_t cbsize)
    {
        size_t rt = 0;    // result.
        if (NULL==pbuf)    return rt;
    
        // 根据 TUINT 类型批量处理数据.
        size_t cntBlock = cbsize / sizeof(TUINT);    // 块数。TUINT类型 能一次处理多个字节.
        size_t cntRem = cbsize % sizeof(TUINT);    // 剩余数量.
        const TUINT* pt = (const TUINT*)pbuf;
        size_t i;
        for(i = 0; i < cntBlock; ++i)
        {
            rt += TPopcnt::popcnt(*pt);    // 累加.
            ++pt;
        }
    
        // 逐字节处理尾部数据.
        const uint8_t* pb = (const uint8_t*)pt;
        for(i = 0; i < cntRem; ++i)
        {
            rt += TPopcnt::popcnt(*pb);    // 累加.
            ++pb;
        }
    
        return rt;
    }
    
    // 数组的位1计数.
    template<class TPopcnt>
    size_t mpopcnt_array(const void* pbuf, size_t cbsize)
    {
        size_t rt;
    #if INTRIN_WORDSIZE>=64
        rt = mpopcnt_array_internal<TPopcnt, uint64_t>(pbuf, cbsize);
    #else
        rt = mpopcnt_array_internal<TPopcnt, uint32_t>(pbuf, cbsize);
    #endif
        return rt;
    }
    
    // 数组的位1计数_2路循环展开_内部函数.
    template<class TPopcnt, class TUINT>
    inline size_t mpopcnt_array_2loop_internal(const void* pbuf, size_t cbsize)
    {
        size_t rt = 0;    // result.
        size_t rt1 = 0;
        if (NULL==pbuf)    return rt;
    
        // 根据 TUINT 类型批量处理数据.
        size_t cbBlock = sizeof(TUINT)*2;    // 块的字节数.
        size_t cntBlock = cbsize / cbBlock;    // 块数.
        size_t cntRem = cbsize % cbBlock;    // 剩余字节数.
        const TUINT* pt = (const TUINT*)pbuf;
        for(size_t i = 0; i < cntBlock; ++i)
        {
            // 累加.
            rt += TPopcnt::popcnt(pt[0]);
            rt1 += TPopcnt::popcnt(pt[1]);
            // next
            pt += 2;
        }
    
        // 合并
        rt += rt1;
    
        // 处理尾部数据.
        rt += mpopcnt_array_internal<TPopcnt, TUINT>(pt, cntRem);
    
        return rt;
    }
    // 数组的位1计数_2路循环展开.
    template<class TPopcnt>
    size_t mpopcnt_array_2loop(const void* pbuf, size_t cbsize)
    {
        size_t rt;
    #if INTRIN_WORDSIZE>=64
        rt = mpopcnt_array_2loop_internal<TPopcnt, uint64_t>(pbuf, cbsize);
    #else
        rt = mpopcnt_array_2loop_internal<TPopcnt, uint32_t>(pbuf, cbsize);
    #endif
        return rt;
    }
    
    // 数组的位1计数_4路循环展开_内部函数.
    template<class TPopcnt, class TUINT>
    inline size_t mpopcnt_array_4loop_internal(const void* pbuf, size_t cbsize)
    {
        size_t rt = 0;    // result.
        size_t rt1 = 0;
        size_t rt2 = 0;
        size_t rt3 = 0;
        if (NULL==pbuf)    return rt;
    
        // 根据 TUINT 类型批量处理数据.
        size_t cbBlock = sizeof(TUINT)*4;    // 块的字节数.
        size_t cntBlock = cbsize / cbBlock;    // 块数.
        size_t cntRem = cbsize % cbBlock;    // 剩余字节数.
        const TUINT* pt = (const TUINT*)pbuf;
        for(size_t i = 0; i < cntBlock; ++i)
        {
            // 累加.
            rt += TPopcnt::popcnt(pt[0]);
            rt1 += TPopcnt::popcnt(pt[1]);
            rt2 += TPopcnt::popcnt(pt[2]);
            rt3 += TPopcnt::popcnt(pt[3]);
            // next
            pt += 4;
        }
    
        // 合并
        rt += rt1 + rt2 + rt3;
    
        // 处理尾部数据.
        rt += mpopcnt_array_internal<TPopcnt, TUINT>(pt, cntRem);
    
        return rt;
    }
    // 数组的位1计数_4路循环展开.
    template<class TPopcnt>
    size_t mpopcnt_array_4loop(const void* pbuf, size_t cbsize)
    {
        size_t rt;
    #if INTRIN_WORDSIZE>=64
        rt = mpopcnt_array_4loop_internal<TPopcnt, uint64_t>(pbuf, cbsize);
    #else
        rt = mpopcnt_array_4loop_internal<TPopcnt, uint32_t>(pbuf, cbsize);
    #endif
        return rt;
    }
    
    
    // 数组的位1计数. 《Hacker's Delight》的数组的位1计数算法.
    inline size_t mpopcnt_array_hak_internal(const uint32_t* A, size_t n)
    {
        size_t i, j, lim;
        uint32_t s, s8, x;
    
        s = 0;
        for(i=0; i<n; i=i+31)
        {
            lim = i+31;
            if (lim>n)    lim=n;
            s8 = 0;
            for(j=i; j<lim; ++j)
            {
                x = A[j];
                x = x - ( (x>>1) & UINT32_C(0x55555555) );
                x = (x & UINT32_C(0x33333333)) + ( (x >> 2) & UINT32_C(0x33333333) );
                x = ( x + (x >> 4) ) & UINT32_C(0x0f0f0f0f);
                s8 = s8 + x;
            }
            x = (s8 & UINT32_C(0x00ff00ff)) + ( (s8 >> 8) & UINT32_C(0x00ff00ff) );
            x = (x & UINT32_C(0x0000ffff)) + (x>>16);
            s = s + x;
        }
        return (size_t)s;
    }
    size_t mpopcnt_array_hak(const void* pbuf, size_t cbsize)
    {
        size_t cntBlock = cbsize/sizeof(uint32_t);    // 块数.
        size_t cntRem = cbsize % sizeof(uint32_t);    // 剩余字节数.
        const uint32_t* pRem = (const uint32_t*)pbuf + cntBlock;
        size_t rt = mpopcnt_array_hak_internal((const uint32_t*)pbuf, cntBlock);
    #if INTRIN_WORDSIZE>=64
        rt += mpopcnt_array_internal<MPopcnt, uint64_t>(pRem, cntRem);
    #else
        rt += mpopcnt_array_internal<MPopcnt, uint32_t>(pRem, cntRem);
    #endif
        return rt;
    }
    
    //////////////////////////////////////////////////
    // main
    //////////////////////////////////////////////////
    
    
    #define BUFSIZE    16384    // = 32KB{L1 Cache} / (2 * sizeof(uint8_t))
    uint8_t buf[BUFSIZE];
    
    // 测试时的函数类型
    typedef size_t (*TESTPROC)(const void* pbuf, size_t cbsize);
    
    // 进行测试
    void runTest(const char* szname, TESTPROC proc)
    {
        const int testloop = 4000;    // 重复运算几次延长时间,避免计时精度问题.
        const clock_t TIMEOUT = CLOCKS_PER_SEC/2;    // 最短测试时间.
        int i,j,k;
        clock_t    tm0, dt;    // 存储时间.
        double mps;    // M/s.
        double mps_good = 0;    // 最佳M/s. 因线程切换会导致的数值波动, 于是选取最佳值.
        volatile size_t n=0;    // 避免内循环被优化.
        for(i=1; i<=3; ++i)    // 多次测试.
        {
            tm0 = clock();
            // main
            k=0;
            do
            {
                for(j=1; j<=testloop; ++j)    // 重复运算几次延长时间,避免计时开销带来的影响.
                {
                    n = proc(buf, BUFSIZE);    // 避免内循环被编译优化消掉.
                }
                ++k;
                dt = clock() - tm0;
            }while(dt<TIMEOUT);
            // show
            mps = (double)k*testloop*BUFSIZE*CLOCKS_PER_SEC/(1024.0*1024.0*dt);    // k*testloop*BUFSIZE/(1024.0*1024.0) 将数据规模换算为M,然后再乘以 CLOCKS_PER_SEC/dt 换算为M/s .
            if (mps_good<mps)    mps_good=mps;    // 选取最佳值.
            //printf("%s:\t%.0f M/s\t//%u\n", szname, mps, (unsigned)n);
        }
        printf("%s:\t%.0f M/s\t//%u\n", szname, mps_good, (unsigned)n);
    }
    
    // 自动测试普通版和循环展开版.
    template<class TPopcnt>
    void runTest_auto(const char* szname)
    {
        char szbuf[200];
    
        sprintf(szbuf, "mpopcnt_array<%s>", szname);
        runTest(szbuf, mpopcnt_array<TPopcnt>);
    
        sprintf(szbuf, "mpopcnt_array_2loop<%s>", szname);
        runTest(szbuf, mpopcnt_array_2loop<TPopcnt>);
    
        sprintf(szbuf, "mpopcnt_array_4loop<%s>", szname);
        runTest(szbuf, mpopcnt_array_4loop<TPopcnt>);
    }
    
    int main(int argc, char* argv[])
    {
        int i;
        //uint32_t u32 = UINT32_C(0xffffffff);
        //uint64_t u64 = UINT64_C(0xffffffffffffffff);
    
        printf("testpopcnt v1.00 (%dbit)\n", INTRIN_WORDSIZE);
        printf("Compiler: %s\n\n", COMPILER_NAME);
    
        // init buf
        srand( (unsigned)time( NULL ) );
        for (i = 0; i < BUFSIZE; i++) buf[i] = (uint8_t)(rand());
    
        //// popcnt
        //printf("MPopcnt_Base::popcnt(u32):\t%u\n", (unsigned)MPopcnt_Base::popcnt(u32) );
        //printf("MPopcnt_Base::popcnt(u64):\t%u\n", (unsigned)MPopcnt_Base::popcnt(u64) );
        //printf("MPopcnt_Mx86::popcnt(u32):\t%u\n", (unsigned)MPopcnt_Mx86::popcnt(u32) );
        //printf("MPopcnt_Mx86::popcnt(u64):\t%u\n", (unsigned)MPopcnt_Mx86::popcnt(u64) );
    
        //// mpopcnt_array
        //printf("mpopcnt_array<MPopcnt_Base>:\t%u\n", (unsigned)mpopcnt_array<MPopcnt_Base>(&buf, sizeof(buf)) );
        //printf("mpopcnt_array<MPopcnt_Mx86>:\t%u\n", (unsigned)mpopcnt_array<MPopcnt_Mx86>(&buf, sizeof(buf)) );
    
        // 进行测试
    //    runTest("mpopcnt_array<MPopcnt_Base>", mpopcnt_array<MPopcnt_Base>);
    //#ifdef INTRIN_POPCNT
    //    if (getcpuidfield(CPUF_POPCNT))    runTest("mpopcnt_array<MPopcnt_Mx86>", mpopcnt_array<MPopcnt_Mx86>);
    //#endif    // #ifdef INTRIN_POPCNT
    //    runTest("mpopcnt_array<MPopcnt_Table>", mpopcnt_array<MPopcnt_Table>);
    //    runTest("mpopcnt_array<MPopcnt_HakBase>", mpopcnt_array<MPopcnt_HakBase>);
    //    runTest("mpopcnt_array<MPopcnt_HakBaseFast>", mpopcnt_array<MPopcnt_HakBaseFast>);
    //    runTest("mpopcnt_array_hak", mpopcnt_array_hak);
    
        // 进行自动测试
        runTest_auto<MPopcnt_Base>("MPopcnt_Base");
    #ifdef INTRIN_POPCNT
        if (getcpuidfield(CPUF_POPCNT))    runTest_auto<MPopcnt_Mx86>("MPopcnt_Mx86");
    #endif    // #ifdef INTRIN_POPCNT
        runTest_auto<MPopcnt_Table>("MPopcnt_Table");
        runTest_auto<MPopcnt_HakBase>("MPopcnt_HakBase");
        runTest_auto<MPopcnt_HakBaseFast>("MPopcnt_HakBaseFast");
        runTest("mpopcnt_array_hak", mpopcnt_array_hak);
    
        return 0;
    }


    3.2 makefile

      全部代码——

    # flags
    CC = g++
    CFS = -Wall -msse
    
    # args
    RELEASE =0
    BITS =
    CFLAGS =
    
    # [args] 生成模式. 0代表debug模式, 1代表release模式. make RELEASE=1.
    ifeq ($(RELEASE),0)
        # debug
        CFS += -g
    else
        # release
        CFS += -O3 -DNDEBUG
    endif
    
    # [args] 程序位数. 32代表32位程序, 64代表64位程序, 其他默认. make BITS=32.
    ifeq ($(BITS),32)
        CFS += -m32
    else
        ifeq ($(BITS),64)
            CFS += -m64
        else
        endif
    endif
    
    # [args] 使用 CFLAGS 添加新的参数. make CFLAGS="-mpopcnt".
    CFS += $(CFLAGS)
    
    
    .PHONY : all clean
    
    # files
    TARGETS = testpopcnt
    OBJS = testpopcnt.o
    
    all : $(TARGETS)
    
    testpopcnt : $(OBJS)
        $(CC) $(CFS) -o $@ $^
    
    
    testpopcnt.o : testpopcnt.cpp zintrin.h ccpuid.h
        $(CC) $(CFS) -c $<
    
    
    clean :
        rm -f $(OBJS) $(TARGETS) $(addsuffix .exe,$(TARGETS))


    四、编译测试

    4.1 编译

      在以下编译器中成功编译——
    VC2003:x86版。
    VC2005:x86版。
    VC2010:x86版、x64版。
    GCC 4.7.0(Fedora 17 x64):x86版、x64版。
    GCC 4.6.2(MinGW(20120426)):x86版。
    GCC 4.7.1(TDM-GCC(MinGW-w64)):x86版、x64版。
    llvm-gcc-4.2(Mac OS X Lion 10.7.4, Xcode 4.4.1):x86版、x64版。


    4.2 测试

      因虚拟机上的有效率损失,于是仅在真实系统上进行测试。

      系统环境——
    CPU:Intel(R) Core(TM) i3-2310M CPU @ 2.10GHz
    操作系统:Windows 7 SP1 x64版

      然后分别运行VC与GCC编译的Release版可执行文件,即以下4个程序——
    exe\testpopcnt_vc32.exe:VC2010 SP1 编译的32位程序,/O2 /arch:SSE2。
    exe\testpopcnt_vc64.exe:VC2010 SP1 编译的64位程序,/O2 /arch:SSE2。
    exe\testpopcnt_gcc32.exe:GCC 4.7.1(TDM-GCC(MinGW-w64)) 编译的32位程序,-O3 -mpopcnt。
    exe\testpopcnt_gcc64.exe:GCC 4.7.1(TDM-GCC(MinGW-w64)) 编译的64位程序,-O3 -mpopcnt。

      测试结果(使用cmdarg_ui)——

      硬件popcnt最快,其次是查表法。而那些高级软件算法在x86平台上效率较差。
      循环展开并没有取得效果,可能是因超过通用寄存器的数量了。


    参考文献——
    《高效程序的奥秘》, 原书名“Hacker's Delight”. Henry S.Warren 著, 冯速 译. 机械工业出版社, 2004年5月.
    《Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes:1, 2A, 2B, 2C, 3A, 3B, and 3C》044US. August 2012. http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
    《AMD64 Architecture Programmer's Manual Volume 3: General-Purpose and System Instructions》. December 2011. http://developer.amd.com/documentation/guides/Pages/default.aspx#manuals
    《[C] 让VC、BCB支持C99的整数类型(stdint.h、inttypes.h)(兼容GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/08/c99int.html
    《[C] zintrin.h: 智能引入intrinsic函数 V1.01版。改进对Mac OS X的支持,增加INTRIN_WORDSIZE宏》. http://www.cnblogs.com/zyl910/archive/2012/10/01/zintrin_v101.html
    《[C/C++] ccpuid:CPUID信息模块 V1.03版,改进mmx/sse指令可用性检查(使用signal、setjmp,支持纯C)、修正AVX检查Bug》. http://www.cnblogs.com/zyl910/archive/2012/10/13/ccpuid_v103.html
    《[x86]SIMD指令集发展历程表(MMX、SSE、AVX等)》. http://www.cnblogs.com/zyl910/archive/2012/02/26/x86_simd_table.html
    《SIMD(MMX/SSE/AVX)变量命名规范心得》. http://www.cnblogs.com/zyl910/archive/2012/04/23/simd_var_name.html
    《GCC 64位程序的makefile条件编译心得——32位版与64位版、debug版与release版(兼容MinGW、TDM-GCC)》. http://www.cnblogs.com/zyl910/archive/2012/08/14/gcc64_make.html
    《[C#] cmdarg_ui:“简单参数命令行程序”的通用图形界面》.  http://www.cnblogs.com/zyl910/archive/2012/06/19/cmdarg_ui.html
    《[C] 跨平台使用Intrinsic函数范例1——使用SSE、AVX指令集 处理 单精度浮点数组求和(支持vc、gcc,兼容Windows、Linux、Mac)》. http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html


    源码下载——
    https://files.cnblogs.com/zyl910/testpopcnt.rar

    作者:zyl910
    版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0.
  • 相关阅读:
    Chino 操作系统开发日志 (1)
    将Asp.Net Core和corefx移植到.Net 4.0
    C++编写操作系统(1):基于 EFI 的 Bootloader
    第二次作业:支付宝手机软件分析
    第一次作业:以人为鉴 可以明得失
    个人附加作业
    个人作业3---个人总结
    结对编程2---单元测试
    个人作业2---必应词典案例分析
    结对编程作业1
  • 原文地址:https://www.cnblogs.com/zyl910/p/testpopcnt.html
Copyright © 2020-2023  润新知