最も速いのは CPU の popcnt 命令を使用したもので、2 番目に近いのは SSSE3 コードです。最速のポータブルはビットスライス方式で、ルックアップテーブルが続きます: http://www.dalkescientific.com/writings/diary/archive/2011/11/02/faster_popcount_update.html
すべての場合と同様に、負荷をベンチマークする必要があります。次に、遅すぎる場合は最適化します。
AMD Phenom II X2 550 の場合、gcc 4.7.1 (を使用g++ -O3 popcnt.cpp -o popcnt -mpopcnt -msse2
):
Bitslice(7) 1462142 米国; cnt = 32500610
Bitslice(24) 1221985 米国; cnt = 32500610
Lauradoux 2347749 米国; cnt = 32500610
SSE2 8 ビット 790898 us; cnt = 32500610
SSE2 16 ビット 825568 us; cnt = 32500610
SSE2 32 ビット 864665 us; cnt = 32500610
16 ビット LUT 1236739 us; cnt = 32500610
8 ビット LUT 1951629 us; cnt = 32500610
gcc popcount 803173 us; cnt = 32500610
gcc popcountll 7678479 us; cnt = 32500610
FreeBSD バージョン 1 2802681 us; cnt = 32500610
FreeBSD バージョン 2 2167031 us; cnt = 32500610
ウィキペディア #2 4927947 cnt = 32500610
ウィキペディア #3 4212143 cnt = 32500610
HAKMEM 169/X11 3559245 米国; cnt = 32500610
ナイーブ 16182699 私たち; cnt = 32500610
Wegner/Kernigan 12115119 米国; cnt = 32500610
アンダーソン 61045764 米国; cnt = 32500610
8x シフトして 6712049 us を追加。cnt = 32500610
32x シフトして 6662200 us を追加。cnt = 32500610
Intel Core2 Duo E8400 の場合、gcc 4.7.1 (g++ -O3 popcnt.cpp -o popcnt -mssse3
は-mpopcnt
この CPU ではサポートされていません)
Bitslice(7) 1353007 米国; cnt = 32500610
Bitslice(24) 953044 米国; cnt = 32500610
Lauradoux 534697 米国; cnt = 32500610
SSE2 8 ビット 458277 us; cnt = 32500610
SSE2 16 ビット 555278 us; cnt = 32500610
SSE2 32 ビット 634897 us; cnt = 32500610
SSSE3 414542 米国; cnt = 32500610
16 ビット LUT 1208412 us; cnt = 32500610
8 ビット LUT 1400175 us; cnt = 32500610
gcc popcount 5428396 us; cnt = 32500610
gcc popcountll 2743358 us; cnt = 32500610
FreeBSD バージョン 1 3025944 us; cnt = 32500610
FreeBSD バージョン 2 2313264 us; cnt = 32500610
ウィキペディア #2 1570519 cnt = 32500610
ウィキペディア #3 1051828 cnt = 32500610
HAKMEM 169/X11 3982779 米国; cnt = 32500610
ナイーブ 20951420 私たち; cnt = 32500610
ウェグナー/カーニガン 13665630 米国; cnt = 32500610
アンダーソン 6771549 米国; cnt = 32500610
8x シフトして 14917323 us を追加。cnt = 32500610
32x シフトして 14494482 us を追加。cnt = 32500610
Bitslice メソッドは、一度に複数 (7 または 24) のマシン ワードをカウントする並列メカニズムであるため、汎用関数としてはわずかな使いやすさしかありません。http://dalkescientific.com/writings/diary/popcnt.cpp の後:
static inline int popcount_fbsd2(unsigned *buf, int n)
{
int cnt=0;
do {
unsigned v = *buf++;
v -= ((v >> 1) & 0x55555555);
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
v = (v + (v >> 4)) & 0x0F0F0F0F;
v = (v * 0x01010101) >> 24;
cnt += v;
} while(--n);
return cnt;
}
static inline int merging2(const unsigned *data)
{
unsigned count1,count2,half1,half2;
count1=data[0];
count2=data[1];
half1=data[2]&0x55555555;
half2=(data[2]>>1)&0x55555555;
count1 = count1 - ((count1 >> 1) & 0x55555555);
count2 = count2 - ((count2 >> 1) & 0x55555555);
count1+=half1;
count2+=half2;
count1 = (count1 & 0x33333333) + ((count1 >> 2) & 0x33333333);
count2 = (count2 & 0x33333333) + ((count2 >> 2) & 0x33333333);
count1+=count2;
count1 = (count1&0x0F0F0F0F)+ ((count1 >> 4) & 0x0F0F0F0F);
count1 = count1 + (count1 >> 8);
count1 = count1 + (count1 >> 16);
return count1 & 0x000000FF;
}
static inline int merging3(const unsigned *data)
{
unsigned count1,count2,half1,half2,acc=0;
int i;
for(i=0;i<24;i+=3)
{
count1=data[i];
count2=data[i+1];
//w = data[i+2];
half1=data[i+2]&0x55555555;
half2=(data[i+2]>>1)&0x55555555;
count1 = count1 - ((count1 >> 1) & 0x55555555);
count2 = count2 - ((count2 >> 1) & 0x55555555);
count1+=half1;
count2+=half2;
count1 = (count1 & 0x33333333) + ((count1 >> 2) & 0x33333333);
count1 += (count2 & 0x33333333) + ((count2 >> 2) & 0x33333333);
acc += (count1 & 0x0F0F0F0F)+ ((count1>>4) &0x0F0F0F0F);
}
acc = (acc & 0x00FF00FF)+ ((acc>>8)&0x00FF00FF);
acc = acc + (acc >> 16);
return acc & 0x00000FFFF;
}
/* count 24 words at a time, then 3 at a time, then 1 at a time */
static inline int popcount_24words(unsigned *buf, int n) {
int cnt=0, i;
for (i=0; i<n-n%24; i+=24) {
cnt += merging3(buf+i);
}
for (;i<n-n%3; i+=3) {
cnt += merging2(buf+i);
}
cnt += popcount_fbsd2(buf+i, n-i);
return cnt;
}