反転と置ける数の数え方

反転の方法を考えていたが、今ひとつよいアイデアがないので前回の置ける位置の走査時に、すべての方向の結果の論理和をとるのではなく、8要素のベクトルに格納して利用することにした。
例えば上方向へひっくり返すには以下のようにした。


vector unsigned int stop = spu_xor( spu_rlqwbyte( board, 8 ), mask_reverse );
vector unsigned int stone_active = spu_and( mobilities[ 0 ], stone );
vector unsigned int flipper = spu_or( stone_active, spu_rlqwbyte( stone_active, 8 ) );
stop = spu_sel( spu_rlqwbyte( stop, 8 ), stop, pattern_select );
for ( int i = 0; i < 6; i++ ) {
flipper = spu_and( stop, spu_rlqwbyte( flipper, 1 ) );
board = spu_xor( board, flipper );
}
このコードでは、まず対象となる石を入力とし、同じ色の石にたどり着いたらひっくり返すという行為が効果を持たないようにstopを生成する。盤面とXORをとることで白黒両方を変化させ、効果の最大値まで繰り返している。
この方法は一見無駄のように見える。オセロで8方向とも反転させられることなどまずないのだから、無駄な場合は操作を行わない方が合理的だ。
しかし実際に測定してみたところ、ここで条件分岐を入れ、必要のない場合は演算をしないようにすると、-funroll-loopsオプションをつけてコンパイルすると逆に遅くなってしまった。これは__builtin_expect__を使っても改善されない。
SPUはパイプラインの段数が深いため、条件分岐の予測ミスのペナルティが非常に大きい。そのため無駄なループを回す方が条件分岐を挿入するよりも合理的なこともあるということがよく分かった。

また、置かれている石と、次に置くことのできる石の数を数える関数は以下のように実装した。


vector unsigned short count_stones_and_mobility( vector unsigned int board,
vector unsigned int mobility ) {
vector unsigned char cnt_b = spu_cntb( (vector unsigned char)board );
vector unsigned char cnt_m = spu_cntb( (vector unsigned char)mobility );
vector unsigned short summed = spu_sumb( cnt_m, cnt_b );
return spu_add( summed, spu_rlqwbyte( summed, 4 ) );
}
ここではSPUの命令であるspu_cntbとspu_sumbを利用して5回の演算だけで白黒の両方について石の数と置ける石の数の両方を数えている。spu_cntbやspu_sumbがint型のベクトルにも対応していたらもっと効率的だったのにと思うが。
この関数の返り値はshort型の8要素のベクトルで、半分の要素は未定義で、残り半分に結果が入っている。
もう少し考えれば白と黒の差もベクトルに入れられるのではないかと思うが、思いついたら改善したい。