[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


源碼下載——
http://files.cnblogs.com/zyl910/testpopcnt.rar


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM