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])。

參考[C++] 測試硬件popcnt(位1計數)指令與各種軟件算法,利用模板實現靜態多態優化性能popcount 算法分析這兩篇文章

對比 popcount 的各種算法,高效在於能利用並行計算,去掉循環,使用減法和模運算。
通過減1的循環算法(parse/dense)在知道數只有三五位為1(或0)的話,其實效率也不賴。
查表法的效率挺不錯的,如果沒有硬件指令的支持,選用這個是可以的。
Hacker's Delight 中的算法,在開源項目中廣為引用。

總的來說,硬件指令最快,查表其次,然后是Hacker's Delight里的hacker_popcnt實現

gcc的5.50.6 X86 Built-in Functions中提到

The following built-in functions are changed to generate new SSE4.2 instructions when -msse4.2 is used.

int __builtin_popcount (unsigned int)
Generates the popcntl machine instruction.

int __builtin_popcountl (unsigned long)
Generates the popcntl or popcntq machine instruction, depending on the size of unsigned long.

int __builtin_popcountll (unsigned long long)
Generates the popcntq machine instruction.

但事實上在編譯時加入-mpopcnt(同時會定義 __POPCNT__宏)即可讓這些函數生成對應的硬件指令。另外也可以在c文件中添加一行#pragma GCC target ("popcnt")來使其生成硬件指令。當沒有生成硬件指令的時候,其會調用gcc用軟件實現的popcnt函數(該函數是采用了Hacker's Delight這本書里的hacker_popcnt算法)

也就是說gcc的內置popcnt函數對應着硬件指令實現和hacker_popcnt實現。而我想優先使用硬件指令實現,其次是查表法實現。所以就有了如下代碼

static inline uint32_t popcnt32(uint32_t v)
{
#ifdef __POPCNT__
    return __builtin_popcount(v);
#else
    static const uint32_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[(uint8_t)v] + countTable[(uint8_t)(v >> 8)]) + (countTable[(uint8_t)(v >> 16)] + countTable[(uint8_t)(v >> 24)]);
#endif
}

static inline uint32_t popcnt64(uint64_t v)
{
#ifdef __POPCNT__
    return __builtin_popcountll(v);
#else
    return popcnt32((uint32_t)v) + popcnt32((uint32_t)(v >> 32));
#endif
}

static inline uint32_t popcnt_array(uint32_t *x, uint32_t len)
{
    uint32_t cnt0 = 0, cnt1 = 0, seg = len >> 2, res = len & 0x3;
    uint64_t *X = (uint64_t *)x;

    seg <<= 1;
    for (uint32_t i = 0; i < seg; i += 2)
    {
        cnt0 += popcnt64(X[i]);
        cnt1 += popcnt64(X[i + 1]);
    }

    for (uint32_t i = 1; i <= res; i++)
    {
        cnt0 += popcnt32(x[len - i]);
    }

    return cnt0 + cnt1;
}


免責聲明!

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



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