4.1 石数取得の高速化 |
本章では前章までに作成したプログラムの性能改善を行ないます。
主に処理を高速化するために無駄と思える処理を省いたり工夫したりします。
本節では、石数取得関数Board_CountDisks()の高速化を行ないます。
Board_CountDisks()は終盤探索のリーフで呼ばれるので、呼び出し頻度がかなり高い関数です。
よく呼び出される関数を高速化しておくと、全体として大きな高速化を望めます。
現在のBoard_CountDisks()は、呼び出される度に盤面の石を数えるという作業を行なっています。
この処理は時間がかかるので、石数を格納する変数を用意してBoard_CountDisks()ではその値を返すだけにします。
まずBoard構造体にメンバ変数を追加します。
struct _Board { int Disk[NUM_DISK]; int Stack[NUM_STACK]; int *Sp; /* 石数格納変数を追加する */ int DiskNum[3]; };
DiskNumを追加しました。
DiskNum[BLACK]が黒石の数、DiskNum[WHITE]が白石の数、DiskNum[EMPTY]が空きマスの数を表します。
Board_CountDisks()はDiskNumの値を返すだけにします。
int Board_CountDisks(const Board *self, int in_color) { return self->DiskNum[in_color]; }
次に必要なのは、「石数が変更されるとき」にDiskNumの値を変更する処理です。
これにはいくつかの関数で修正が必要です。
void Board_Clear(Board *self) { int i, j; for (i = 0; i < NUM_DISK; i++) { self->Disk[i] = WALL; } for (i = 0; i < BOARD_SIZE; i++) { for (j = 0; j < BOARD_SIZE; j++) { self->Disk[Board_Pos(i, j)] = EMPTY; } } self->Disk[E4] = BLACK; self->Disk[D5] = BLACK; self->Disk[D4] = WHITE; self->Disk[E5] = WHITE; self->Sp = self->Stack; /* 石数の初期化 */ self->DiskNum[BLACK] = 2; self->DiskNum[WHITE] = 2; self->DiskNum[EMPTY] = BOARD_SIZE * BOARD_SIZE - 4; }
着手を行なうと石数が変わるので、Board_Flip()を以下のように修正します。
N石返したら自分の石をN+1石増やし、相手の石をN石減らし、空きマスを1個減らします。
int Board_Flip(Board *self, int in_color, int in_pos) { int result = 0; if (self->Disk[in_pos] != EMPTY) { return 0; } result += Board_FlipLine(self, in_color, in_pos, DIR_UP_LEFT); result += Board_FlipLine(self, in_color, in_pos, DIR_UP); result += Board_FlipLine(self, in_color, in_pos, DIR_UP_RIGHT); result += Board_FlipLine(self, in_color, in_pos, DIR_LEFT); result += Board_FlipLine(self, in_color, in_pos, DIR_RIGHT); result += Board_FlipLine(self, in_color, in_pos, DIR_DOWN_LEFT); result += Board_FlipLine(self, in_color, in_pos, DIR_DOWN); result += Board_FlipLine(self, in_color, in_pos, DIR_DOWN_RIGHT); if (result > 0) { self->Disk[in_pos] = in_color; BOARD_STACK_PUSH(self, in_pos); BOARD_STACK_PUSH(self, OPPONENT_COLOR(in_color)); BOARD_STACK_PUSH(self, result); /* 石数の変更 */ self->DiskNum[in_color] += result + 1; self->DiskNum[OPPONENT_COLOR(in_color)] -= result; self->DiskNum[EMPTY]--; } return result; }
着手したら石数が変わるので、逆に1手戻す場合にも石数が変わります。
着手の場合と逆になるように石数の変更を行ないます。
int Board_Unflip(Board *self) { int result; int i, color; if (self->Sp <= self->Stack) { return 0; } result = BOARD_STACK_POP(self); color = BOARD_STACK_POP(self); self->Disk[BOARD_STACK_POP(self)] = EMPTY; for (i = 0; i < result; i++) { self->Disk[BOARD_STACK_POP(self)] = color; } /* 石数の変更 */ self->DiskNum[color] += result; self->DiskNum[OPPONENT_COLOR(color)] -= result + 1; self->DiskNum[EMPTY]++; return result; }
最後に盤面反転処理Board_Reverse()の修正を行ないます。
void Board_Reverse(Board *self) { int pos; int *p; int n; for (pos = 0; pos < NUM_DISK; pos++) { if (self->Disk[pos] == BLACK) { self->Disk[pos] = WHITE; /* 石数の変更 */ self->DiskNum[BLACK]--; self->DiskNum[WHITE]++; } else if (self->Disk[pos] == WHITE) { self->Disk[pos] = BLACK; /* 石数の変更 */ self->DiskNum[WHITE]--; self->DiskNum[BLACK]++; } } p = self->Sp; for (p = self->Sp; p > self->Stack;) { p--; n = *p; p--; *p = OPPONENT_COLOR(*p); p -= n + 1; } }
では修正したソースコードをコンパイルして実行してみましょう。
終盤探索は速くなったでしょうか?
筆者が試したところでは終盤探索が1.5倍くらい早くなっていました。
高速化はされていますが、いまひとつという感じもあります。
実はまだまだ無駄な処理が多いのです。
次節からさらなる高速化を進めていきます。