sakurapyon’s blog

sakurapyon’s blog

サーバーの速度 / 指し手生成速度

sakurapyonの居候しているサーバーは /proc/cpuinfo によると Intel(R) Xeon(R) E5645, 2.4GHz, cache 4MB というものらしいんだけど、これって他のマシンに比べてどれだけ速い(遅い)んだろうか?

世の中にはスーパーπランキングというのがあって、CPUごとのランキングが掲載されている。Super Πは整数演算なので、コンピュータ将棋の速度の参考にはなるだろう。将棋の方は置換表とかのメモリアクセスも絡むけど、まあいいや。

Kanada Lab Homepage japanese versionからSuperΠのバイナリをダウンロード。コンパイルオプションがCPUに合ってないかもしれないけど、だぶん条件は皆一緒だろう。419万桁実行するとこんな感じだ。

$ ../super_pi/pi 22
 Version 2.0 of the super_pi for Linux OS
 Fortran source program was translated into C program with version 19981204 of
 f2c, then generated C source program was optimized manually.
 pgcc 3.2-3 with compile option of "-fast -tp px -Mbuiltin -Minline=size:1000 -Mnoframe -Mnobounds -Mcache_align -Mdalign -Mnoreentrant" was used for the
 compilation.
 ------ Started super_pi run : 2012年 4月 15日 日曜日 10:37:48 JST
 Start of PI calculation up to 4194304 decimal digits
 End of initialization. Time=       1.603 Sec.
 I= 1 L=       0        Time=       4.300 Sec.
 I= 2 L=       0        Time=       4.865 Sec.
 I= 3 L=       1        Time=       4.156 Sec.
 I= 4 L=       2        Time=       3.825 Sec.
 I= 5 L=       5        Time=       4.615 Sec.
 I= 6 L=      10        Time=       5.058 Sec.
 I= 7 L=      21        Time=       4.351 Sec.
 I= 8 L=      43        Time=       4.109 Sec.
 I= 9 L=      87        Time=       3.882 Sec.
 I=10 L=     174        Time=       3.910 Sec.
 I=11 L=     349        Time=       4.306 Sec.
 I=12 L=     698        Time=       4.490 Sec.
 I=13 L=    1396        Time=       4.319 Sec.
 I=14 L=    2794        Time=       3.761 Sec.
 I=15 L=    5588        Time=       4.046 Sec.
 I=16 L=   11176        Time=       4.377 Sec.
 I=17 L=   22353        Time=       3.903 Sec.
 I=18 L=   44707        Time=       4.029 Sec.
 I=19 L=   89415        Time=       3.940 Sec.
 I=20 L=  178831        Time=       3.619 Sec.
 I=21 L=  357662        Time=       3.339 Sec.
 End of main loop
 End of calculation.    Time=      91.380 Sec.
 End of data output.    Time=       0.340 Sec.
 Total calculation(I/O) time=      91.720(       8.500) Sec.
 ------ Ended super_pi run : 2012年 4月 15日 日曜日 10:39:20 JST


90秒ちょいか。8bit機の頃を考えると桁違いに速いな。測るたびに1割ぐらい速度が変わるのはvpsのせいだろうなー。

スーパーπランキングの上位はover clockされているものばかり。Core i7 2600k@3.40GHzが44秒なのでクロック比からすると遅めな気がする。

ついでに指し手生成速度: gcc version 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC)
もうちょっと速くならんかなー。AddMovesがまずいんだと思うけど。simkみたいに事前に手を用意して配列アクセスの方が早いだろうか? (悩みどころ)

↓この表間違ってます。追記あり

PGOなし PGOあり
初期局面 614,860.19回/秒 1,689,226.28回/秒 30手
指し手生成祭り局面 465,374.94回/秒 1,038,586.61回/秒 198手

NHK杯見なきゃ…

追記:元の方は-pg残ってたんで測りなおし。ついでに always inline 入れて高速化してみた。

-pg付き PGOなし PGOあり
初期局面 627197.94回/秒 1638551.85回/秒 1766550.37回/秒 30手
指し手生成祭り局面 611332.52回/秒 1379780.42回/秒 1471510.09回/秒 198手

PGOの効果はご指摘の通り1割ぐらいでした。すんません。
always inlineの方が効果ありました(^_^;

駒打ちルーチン

たぶん、指し手がそこそこ速いのは姑息な展開が利いているのだと思います。こんな感じ。
桂香歩打ちはあまりいじってないです。

class Te {
public:
        uint64  T;
        inline bool IsNull() {
                return (T&0x0ffff)==0;
        }
        inline Te() {};
        inline Te(unsigned char f,unsigned char t,KomaInf k,KomaInf c,int p=0,int K=0 ,short v=0) : T     ((uint64)t
                        | (f<<8)
                        | (k<<16)
                        | (c<<24)
                        | ((p==0)?0:(1ULL<<31))
                        | ((uint64)v)<<32)
        {
                (void)K;
        }
(中略)
//      駒打ち特化ルーチン
//
template <int hands,int player>inline void hGenDropHands(int &teNum, Te *teBuf, int n, unsigned char *empty)
{
        for(int pos=0;pos<n;pos++) {
                if(hands&(1<<3))        teBuf[teNum++]=Te(0,empty[pos],player|GI,0,0);
                if(hands&(1<<4))        teBuf[teNum++]=Te(0,empty[pos],player|KI,0,0);
                if(hands&(1<<5))        teBuf[teNum++]=Te(0,empty[pos],player|KA,0,0);
                if(hands&(1<<6))        teBuf[teNum++]=Te(0,empty[pos],player|HI,0,0);
        }
}
//
//      打つ手を生成する
//
template <int player>inline void hGenDrop(Kyokumen &k,int &teNum, Te *teBuf, int n, unsigned char *empty)
{
        const   int     hands=(player==SELF)?
                                ((k.Hand[SGI]>0?(1<<3):0)
                                |(k.Hand[SKI]>0?(1<<4):0)
                                |(k.Hand[SKA]>0?(1<<5):0)
                                |(k.Hand[SHI]>0?(1<<6):0)):
                                ((k.Hand[EGI]>0?(1<<3):0)
                                |(k.Hand[EKI]>0?(1<<4):0)
                                |(k.Hand[EKA]>0?(1<<5):0)
                                |(k.Hand[EHI]>0?(1<<6):0));
        switch (hands){
        case    0x00:   break;
        case    0x08:   return  hGenDropHands<0x08,player>(teNum,teBuf,n,empty);
        case    0x10:   return  hGenDropHands<0x10,player>(teNum,teBuf,n,empty);
        case    0x18:   return  hGenDropHands<0x18,player>(teNum,teBuf,n,empty);
        case    0x20:   return  hGenDropHands<0x20,player>(teNum,teBuf,n,empty);
        case    0x28:   return  hGenDropHands<0x28,player>(teNum,teBuf,n,empty);
        case    0x30:   return  hGenDropHands<0x30,player>(teNum,teBuf,n,empty);
        case    0x38:   return  hGenDropHands<0x38,player>(teNum,teBuf,n,empty);
        case    0x40:   return  hGenDropHands<0x40,player>(teNum,teBuf,n,empty);
        case    0x48:   return  hGenDropHands<0x48,player>(teNum,teBuf,n,empty);
        case    0x50:   return  hGenDropHands<0x50,player>(teNum,teBuf,n,empty);
        case    0x58:   return  hGenDropHands<0x58,player>(teNum,teBuf,n,empty);
        case    0x60:   return  hGenDropHands<0x60,player>(teNum,teBuf,n,empty);
        case    0x68:   return  hGenDropHands<0x68,player>(teNum,teBuf,n,empty);
        case    0x70:   return  hGenDropHands<0x70,player>(teNum,teBuf,n,empty);
        case    0x78:   return  hGenDropHands<0x78,player>(teNum,teBuf,n,empty);
        }

// 有効そうな手を生成する
// pinには必ず有効なpin情報がある前提
template <int SorE>int Kyokumen::hMakeEffectiveMoves(Te *teBuf,char *pin)
{
        int teNum=0;
        int suji,dan;
        int     nEmpty=0;
        unsigned char   Empty[83];
        Empty[nEmpty++]=0;
        // 盤上の駒を動かす
        for(dan=1;dan<=9;dan++) {
                for(suji=0x10;suji<=0x90;suji+=0x10) {
                        if (ban[suji+dan]==EMPTY) {
                                Empty[nEmpty++]=suji+dan;
                        }else if (ban[suji+dan]&SorE) {
                                AddMovesPromote(SorE,teNum,teBuf,ban[suji+dan],suji+dan,pin[suji+dan]);
                        }
                }
        }
        Empty[nEmpty]=0x9a;
        if(SorE==SELF){
                hGenDrop<SELF>(*this,teNum,teBuf,nEmpty-1,&Empty[1]);
        }else{
                hGenDrop<ENEMY>(*this,teNum,teBuf,nEmpty-1,&Empty[1]);
        }
  (以下略)
}

今気がついた。voidな関数の戻り値をreturnで返してやがる(>_<)。直そう…
持ち駒をbitで持ってればもうちょっと速くなるんだけど、改造箇所が増えまくるからなあ。