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;
}