とやると、JASRACから物言いがきちゃうかな>志村さん:)
といっても、ここでは枕木の数でもレールの継ぎ目の数でもなく、
TAKESAKO @ Yet another Cybozu Labs: Binary Hacks と 64bit popCount 問題64bitの数値の中で1になっているビット数を数える
のですが。
ちょっとMMX/SSE/3DNow!は専門外、というかOS Xでどうやるのかがまだよく分かっていないので。
自分でも試してみたのですが、実に面白い結果が出ました。
ここでは、なぜかベンチマークから抜けていた一番「すぐに思いつく」実装と、一番「定義通り」の実装を加えています。
unsigned charにcastしてバイト列として数える
int popCount64bitC(unsigned long long x) {
unsigned char *p = (unsigned char *)&x;
return popTable[p[0]] + popTable[p[1]] + popTable[p[2]] + popTable[p[3]]
+ popTable[p[4]] + popTable[p[5]] + popTable[p[6]] + popTable[p[7]];
}
文字通りbitを一つ一つ丁寧に数える
int popCount64bitO(unsigned long long x) {
int i;
int ret = 0;
for (i = 0; i < 64; i++){
ret += (x & 0x1);
x >>= 1;
}
return ret;
}
それを、以下のようにbenchmarkしてみました。
#include <stdio.h>
#include <stdlib.h>
#include <sys/times.h>
#include <sys/resource.h>
#include "popcount_init.c"
#include "popcount_a.c"
#include "popcount_b.c"
#include "popcount_c.c"
#include "popcount_o.c"
#define MILLION 1000000
#define MAXU64 0xffffffffffffffffULL
double ud(struct rusage *s, struct rusage *e){
double d = e->ru_utime.tv_sec - s->ru_utime.tv_sec;
d *= MILLION;
d += e->ru_utime.tv_usec - s->ru_utime.tv_usec;
return d / MILLION;
}
double sd(struct rusage *s, struct rusage *e){
double d = e->ru_stime.tv_sec - s->ru_stime.tv_sec;
d *= MILLION;
d += e->ru_stime.tv_usec - s->ru_stime.tv_usec;
return d / MILLION;
}
int main(int argc, char **argv){
struct rusage ru_s, ru_e;
int repeat = 100*MILLION;
int i;
int popcount = 0;
init_popTable();
if (argc > 1){
repeat = atol(argv[1]);
}
printf("repeat = %d\n", repeat);
getrusage(0, &ru_s);
for(i = 0; i < repeat; i++) popcount = popCount64bitA(MAXU64 - i);
getrusage(0, &ru_e);
printf("popcount(0x%qx) = %d\n", MAXU64 - i, popcount);
printf("A :user: %g, system: %g\n", ud(&ru_s, &ru_e), sd(&ru_s, &ru_e));
getrusage(0, &ru_s);
for(i = 0; i < repeat; i++) popcount = popCount64bitB(MAXU64 - i);
getrusage(0, &ru_e);
printf("popcount(0x%qx) = %d\n", MAXU64 - i, popcount);
printf("B: user: %g, system: %g\n", ud(&ru_s, &ru_e), sd(&ru_s, &ru_e));
getrusage(0, &ru_s);
for(i = 0; i < repeat; i++) popcount = popCount64bitC(MAXU64 - i);
getrusage(0, &ru_e);
printf("popcount(0x%qx) = %d\n", MAXU64 - i, popcount);
printf("C: user: %g, system: %g\n", ud(&ru_s, &ru_e), sd(&ru_s, &ru_e));
getrusage(0, &ru_s);
for(i = 0; i < repeat; i++) popcount = popCount64bitO(MAXU64 - i);
getrusage(0, &ru_e);
printf("popcount(0x%qx) = %d\n", MAXU64 - i, popcount);
printf("O: user: %g, system: %g\n", ud(&ru_s, &ru_e), sd(&ru_s, &ru_e));
return 0;
}
そして、その結果。MacBook Pro 2.0GHz上。
% gcc -O0 -W -Wall popcount.c % ./a.out 10000000 repeat = 10000000 popcount(0xffffffffff67697f) = 50 A :user: 0.448425, system: 0.001029 popcount(0xffffffffff67697f) = 50 B: user: 0.525935, system: 0.001246 popcount(0xffffffffff67697f) = 50 C: user: 0.282275, system: 0.000749 popcount(0xffffffffff67697f) = 50 O: user: 3.0922, system: 0.006753 % gcc -O6 -W -Wall popcount.c % ./a.out 10000000 repeat = 10000000 popcount(0xffffffffff67697f) = 50 A :user: 0.137518, system: 0.000396 popcount(0xffffffffff67697f) = 50 B: user: 0.224507, system: 0.000559 popcount(0xffffffffff67697f) = 50 C: user: 0.200149, system: 0.000528 popcount(0xffffffffff67697f) = 50 O: user: 1.33571, system: 0.003054
見ての通り、Optimizeするのとしないのとでは大違いで、Optimizeする前は、Unsigned Charとして扱った方が早いという以外な結果が出たのです。そして、どちらの場合も、BよりAの方が早い。これってCacheにLookup Tableが乗り切るかどうかの差にも思えてきました。
いや、64bitなので、32bit Registerに乗り切らないからという理由もありえる。というわけで、32bit版でやってみたのが以下です。
% gcc -O0 -W -Wall popcount.c % ./a.out 10000000 repeat = 10000000 popcount(0xff67697f) = 18 A :user: 0.17685, system: 0.000537 popcount(0xff67697f) = 18 B: user: 0.181075, system: 0.000551 popcount(0xff67697f) = 18 C: user: 0.159041, system: 0.000483 popcount(0xff67697f) = 18 O: user: 1.17174, system: 0.002695 % gcc -O6 -W -Wall popcount.c % ./a.out 10000000 repeat = 10000000 popcount(0xff67697f) = 18 A :user: 0.047296, system: 0.000216 popcount(0xff67697f) = 18 B: user: 0.074523, system: 0.000146 popcount(0xff67697f) = 18 C: user: 0.105888, system: 0.000279 popcount(0xff67697f) = 18 O: user: 0.564702, system: 0.001144
こちらでも、BよりAの方が早いのです。-Sの出力を見てみてもちょっとわからないなー。
Dan the Occasional Binarian
このブログにコメントするにはログインが必要です。
さんログアウト
この記事には許可ユーザしかコメントができません。