ビットを数える - アセンブラで計算 vs テーブル引き vs ハッカー技巧 南洋の大決戦
ggさん(加藤(gg)さんと思います)にコメント欄で提案を頂きましたが、
今回、IBMが送り出した人類の英知とも言うべきバージョン5を加えてみました。
結論から言うと、アセンブラで計算(バージョン3)の大勝利のようです。
はてな記法に合わせて出力させました。
前回最適化オプション無しでしたが、
今回/Otを付けています。
PopCnt32がアセンブラで計算
PopCnt32bが256表を使い回し
PopCnt32cがバージョン5の分岐無し、表引き無し
bとcも大差ないですね。他の人が試した場合ではbよりcが遅かった例もあるようです。
00000000 | PopCnt32 | 0 | 0.5s |
00000000 | PopCnt32b | 0 | 1.3s |
00000000 | PopCnt32c | 0 | 1.2s |
00000001 | PopCnt32 | 90000001 | 0.5s |
00000001 | PopCnt32b | 90000001 | 1.3s |
00000001 | PopCnt32c | 90000001 | 1.2s |
00000002 | PopCnt32 | 90000001 | 0.6s |
00000002 | PopCnt32b | 90000001 | 1.3s |
00000002 | PopCnt32c | 90000001 | 1.2s |
00000003 | PopCnt32 | 180000002 | 0.7s |
00000003 | PopCnt32b | 180000002 | 1.3s |
00000003 | PopCnt32c | 180000002 | 1.2s |
00000004 | PopCnt32 | 90000001 | 0.5s |
00000004 | PopCnt32b | 90000001 | 1.3s |
00000004 | PopCnt32c | 90000001 | 1.2s |
00000005 | PopCnt32 | 180000002 | 0.8s |
00000005 | PopCnt32b | 180000002 | 1.3s |
00000005 | PopCnt32c | 180000002 | 1.2s |
00000006 | PopCnt32 | 180000002 | 0.7s |
00000006 | PopCnt32b | 180000002 | 1.3s |
00000006 | PopCnt32c | 180000002 | 1.2s |
00000007 | PopCnt32 | 270000003 | 0.9s |
00000007 | PopCnt32b | 270000003 | 1.3s |
00000007 | PopCnt32c | 270000003 | 1.2s |
00000008 | PopCnt32 | 90000001 | 0.5s |
00000008 | PopCnt32b | 90000001 | 1.3s |
00000008 | PopCnt32c | 90000001 | 1.2s |
00000009 | PopCnt32 | 180000002 | 0.6s |
00000009 | PopCnt32b | 180000002 | 1.3s |
00000009 | PopCnt32c | 180000002 | 1.2s |
0000000a | PopCnt32 | 180000002 | 0.6s |
0000000a | PopCnt32b | 180000002 | 1.3s |
0000000a | PopCnt32c | 180000002 | 1.2s |
0000000b | PopCnt32 | 270000003 | 0.8s |
0000000b | PopCnt32b | 270000003 | 1.3s |
0000000b | PopCnt32c | 270000003 | 1.2s |
0000000c | PopCnt32 | 180000002 | 0.6s |
0000000c | PopCnt32b | 180000002 | 1.3s |
0000000c | PopCnt32c | 180000002 | 1.2s |
0000000d | PopCnt32 | 270000003 | 0.7s |
0000000d | PopCnt32b | 270000003 | 1.3s |
0000000d | PopCnt32c | 270000003 | 1.2s |
0000000e | PopCnt32 | 270000003 | 0.7s |
0000000e | PopCnt32b | 270000003 | 1.3s |
0000000e | PopCnt32c | 270000003 | 1.2s |
0000000f | PopCnt32 | 360000004 | 0.9s |
0000000f | PopCnt32b | 360000004 | 1.3s |
0000000f | PopCnt32c | 360000004 | 1.2s |
ビットベクトルが疎で、ほとんどビットが立ってないと仮定すると、
0〜fまでの間は、
完全にアセンブラで計算が速いようです。
検索するとサイボーズラボの中の人が「ビットを数える」をテーマで書いてましたが、
それもフルビット立った場合とか、32ビットの半分立ってるとか、
明らかにバージョン3に不利な局面を仮定しておいて、バージョン3の負けを宣告してました。
実際に使う場面を想定しないと比較は成り立たないと思う
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <sys/types.h> #include <math.h> #include <windows.h> __forceinline unsigned int PopCnt32(unsigned int a) { __asm { mov ecx, dword ptr a xor eax, eax test ecx, ecx jz l1 l0: lea edx, [ecx - 1] inc eax and ecx, edx jnz l0 l1: } } const int Tbl[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 }; // xは、何ビット1が立っているか __forceinline unsigned int PopCnt32b(unsigned int x) { return Tbl[((unsigned char*)&x)[0]]+Tbl[((unsigned char*)&x)[1]]+Tbl[((unsigned char*)&x)[2]]+Tbl[((unsigned char*)&x)[3]]; } __forceinline int PopCnt32c(unsigned int bits) { bits = (bits & 0x55555555) + (bits >> 1 & 0x55555555); bits = (bits & 0x33333333) + (bits >> 2 & 0x33333333); bits = (bits & 0x0f0f0f0f) + (bits >> 4 & 0x0f0f0f0f); bits = (bits & 0x00ff00ff) + (bits >> 8 & 0x00ff00ff); return (bits & 0x0000ffff) + (bits >>16 & 0x0000ffff); } clock_t t_end; clock_t t_start; void main() { long count=90000000; int sum=0; float thinktime; for(int j=0x00000000;j<=0x0000000f;j+=0x00000001) { t_start = clock(); sum=0; for(long i=0;i<=count;i++) sum+=PopCnt32(j); t_end = clock(); thinktime=(float)(t_end - t_start) / CLOCKS_PER_SEC; printf("|%08x|PopCnt32 |%d|%4.1fs|\n",j,sum,thinktime ); t_start = clock(); int sum=0; for(long i=0;i<=count;i++) sum+=PopCnt32b(j); t_end = clock(); thinktime=(float)(t_end - t_start) / CLOCKS_PER_SEC; printf("|%08x|PopCnt32b|%d|%4.1fs|\n",j,sum,thinktime ); t_start = clock(); sum=0; for(long i=0;i<=count;i++) sum+=PopCnt32c(j); t_end = clock(); thinktime=(float)(t_end - t_start) / CLOCKS_PER_SEC; printf("|%08x|PopCnt32c|%d|%4.1fs|\n",j,sum,thinktime ); } }
飛び利きがビット1個立ってると仮定すると
00010000 | PopCnt32 | 90000001 | 0.5s |
00010000 | PopCnt32b | 90000001 | 1.3s |
00010000 | PopCnt32c | 90000001 | 1.2s |
00010001 | PopCnt32 | 180000002 | 0.6s |
00010001 | PopCnt32b | 180000002 | 1.3s |
00010001 | PopCnt32c | 180000002 | 1.2s |
00010002 | PopCnt32 | 180000002 | 0.6s |
00010002 | PopCnt32b | 180000002 | 1.3s |
00010002 | PopCnt32c | 180000002 | 1.2s |
00010003 | PopCnt32 | 270000003 | 0.7s |
00010003 | PopCnt32b | 270000003 | 1.3s |
00010003 | PopCnt32c | 270000003 | 1.2s |
00010004 | PopCnt32 | 180000002 | 0.6s |
00010004 | PopCnt32b | 180000002 | 1.3s |
00010004 | PopCnt32c | 180000002 | 1.2s |
00010005 | PopCnt32 | 270000003 | 0.7s |
00010005 | PopCnt32b | 270000003 | 1.3s |
00010005 | PopCnt32c | 270000003 | 1.2s |
00010006 | PopCnt32 | 270000003 | 0.7s |
00010006 | PopCnt32b | 270000003 | 1.3s |
00010006 | PopCnt32c | 270000003 | 1.2s |
00010007 | PopCnt32 | 360000004 | 0.8s |
00010007 | PopCnt32b | 360000004 | 1.3s |
00010007 | PopCnt32c | 360000004 | 1.2s |
00010008 | PopCnt32 | 180000002 | 0.6s |
00010008 | PopCnt32b | 180000002 | 1.3s |
00010008 | PopCnt32c | 180000002 | 1.2s |
00010009 | PopCnt32 | 270000003 | 0.7s |
00010009 | PopCnt32b | 270000003 | 1.3s |
00010009 | PopCnt32c | 270000003 | 1.2s |
0001000a | PopCnt32 | 270000003 | 0.7s |
0001000a | PopCnt32b | 270000003 | 1.3s |
0001000a | PopCnt32c | 270000003 | 1.2s |
0001000b | PopCnt32 | 360000004 | 0.8s |
0001000b | PopCnt32b | 360000004 | 1.3s |
0001000b | PopCnt32c | 360000004 | 1.2s |
0001000c | PopCnt32 | 270000003 | 0.7s |
0001000c | PopCnt32b | 270000003 | 1.3s |
0001000c | PopCnt32c | 270000003 | 1.2s |
0001000d | PopCnt32 | 360000004 | 0.8s |
0001000d | PopCnt32b | 360000004 | 1.3s |
0001000d | PopCnt32c | 360000004 | 1.2s |
0001000e | PopCnt32 | 360000004 | 0.8s |
0001000e | PopCnt32b | 360000004 | 1.3s |
0001000e | PopCnt32c | 360000004 | 1.2s |
0001000f | PopCnt32 | 450000005 | 1.0s |
0001000f | PopCnt32b | 450000005 | 1.3s |
0001000f | PopCnt32c | 450000005 | 1.2s |
若干遅くなったけど、それでもアセンブラで計算が速いです
というわけで、Craftyの中の人もバージョン5は辞めて、バージョン3を使った方がいいと思う
特に利き0の時のバージョン3の速さは凄い。
ただ、味方駒全体のbitboardのPopCntみたいな場合は、16個ぐらい立つのでテーブル引いたりの方がいいですね(^^;