並列探索

並列探索は、複数のスレッドやコアを使って探索を高速化する技術群である。 現代の将棋AIやチェスエンジンでは、マルチコア CPU を活かすために重要な分野である。

概要

探索木は基本的に逐次的な構造を持つため、完全にきれいな並列化は難しい。 それでも実際には、

  • ルートで手を分ける
  • 部分木を分担する
  • 置換表を共有する

などの方法で性能向上を狙う。

Chessprogramming Wiki でも、

が代表的な方式として挙げられている。

なぜ難しいのか

逐次探索では、

  • 先に読んだ枝の結果が
  • 後続の枝刈り条件に影響する

ため、単純に仕事を分けるだけではうまくいかない。

また、共有メモリでは

  • 置換表の競合
  • PV 情報の整合性
  • スレッド間同期コスト

も問題になる。

代表的な方式

Young Brothers Wait Concept

最初の有望手を先に読み、その結果を使って後続の仕事を並列化する考え方。 理論的には洗練されているが、実装はやや複雑になる。

Lazy SMP

複数スレッドが似た探索をほぼ独立に進め、 共有置換表を通じてゆるく協調する方式。 実装が比較的簡単で、実用エンジンで広く使われている。

実装イメージ

ルートで簡単に分担するイメージは次のようになる。

void parallelRootSearch(const Position& root, std::vector<Move> moves) {
    parallel_for_each(moves, [&](Move move) {
        Position next = doMove(root, move);
        int score = -search(next, depth - 1, -INF, INF);
        storeRootResult(move, score);
    });
}

実際には、単純なルート分割だけでは負荷の偏りが大きいため、 共有テーブルやスレッド管理を併用する。

将棋AIでの位置づけ

将棋AIは 1 手あたりの探索量が大きいので、マルチスレッド化の恩恵が大きい。 一方で、

を共有するため、メモリ帯域や NUMA の影響も受けやすい。

注意点

  • NPS が増えても、時間当たりの実力向上は線形には伸びない
  • 同期が多いとスケーリングが悪くなる
  • 評価器やハッシュのサイズが大きいとメモリ帯域がボトルネックになりやすい

関連項目

参考にしたホームページ