連載やねうら王miniで遊ぼう!10日目

今回は、将棋所で遊べるランダムプレイヤーの作り方を説明していきます。

search.cppの4つの関数

search.cppのほうに次の4つの関数があります。自分で書く場合は中身は要らないので好きな様に書き換えて大丈夫です。

1. Search::init()は起動時に一度だけ呼びだされる。探索で用いる定数テーブルの初期化などはここに書く。
2. Search::clear()は対局開始時に毎回呼び出される。置換表のクリアなど、時間のかかる初期化処理はここに書く。
3. MainThread::think()はUSIプロトコルのgoコマンドの応答として呼び出される。探索を行なうメインスレッドはここから実行される。
4. Thread::search()は、探索を並列化するときにメインスレッド以外はここから実行される。メインスレッドもThread::search()を実行したいときは、MainThread::thinkのなかから明示的に呼び出すと良い。

将棋所で遊べるランダムプレイヤー

将棋所で遊べるランダムプレイヤーを作ってみよう。

いま、1.と2.は関係ないので、並列化もしないので4.も関係ない。3.に書けば良い。

3.に書くときに、これはMainThreadというclassのthink()を実装していることになるので、MainThreadのメンバーにアクセスできる。MainThreadのメンバーとして、以下の変数にアクセスできる。

rootPos : 探索開始時の局面
rootMoves : 探索開始局面での合法手

後者は少し特殊な構造体なのでいまはこれを使わずに前者だけ使って合法手を生成して、そのなかからランダムに1つ指し手を選ぶようにしてみよう。

これですべてである。ランダムプレイヤー完成だ。

スクリーンショット 2015-12-20 10.10.32

確かに将棋所で対戦できている。ランダムプレイヤーなのですこぶる弱いが、たった3行書くだけでルール通り指せるということに感動しないだろうか。

上のプログラムを解説しておこう。

「ml.size()==0」というのは「生成された合法手がない」場合で、これは詰みの局面であるから、投了(MOVE_RESIGN)する。これはUSIプロトコルでは”bestmove resign”とGUIに送れば、良いことになっている。見ての通り、Moveという指し手を表すenumにMOVE_RESIGNという定数を入れておくと、<< で標準出力に表示させるときに”resign”という文字列が出力される。

sync_out~sync_endl

sync_cout~sync_endlは、この出力をしているときに他のスレッドが統計情報などを標準出力に出力してしまうと文字列が分断されたりしておかしくなるので、スレッド排他をしながら標準出力に出力するための呪文である。思考中に標準出力に出力したいときは常にこの呪文を使うと良い。

これは、Stockfishと同じく次のようなマクロにより実現してある。

並列化

やねうら王miniを用いれば、探索の並列化も簡単に出来る。

実行結果は以下のようになる。

スレッドidはMainThreadは必ず0である。slaveスレッドは1,2,3,…のように1から連番となる。

また、スレッド数はUSIプロトコルのoptionで設定された数だけ走る。

なお、このスレッド数は将棋所では、思考エンジン設定のところで変更出来るようになっている。(このへんの仕組みについてはまたの機会に詳しく書く)

YBWCを超えて

従来、将棋の探索の並列化はYBWC(Young Brothers Wait Concept)を用いるのが主流であった。Bonanza6からponanza(2015)に至るまで並列化はすべてYBWCである。

YBWCについて詳しいことはggrksである。

簡単に言うと、残り探索深さがたくさんあるときに、そのnodeを複数のスレッドで探索する。この、nodeをそれぞれのスレッドに割り振ることを(nodeの)”split”と呼ぶ。split後、それぞれのスレッドはそのnodeの指し手を分担して調べていく。

従来、どのタイミングでsplitするか、そのときにいくつのスレッドに割り振るかなどの戦略が探索上、重要なテクニックであった。

しかし、YBWCはもう終焉を迎えた。

近年、もっといい方法が発明されたのだ。これは探索(の並列化)における一大ブレイクスルーである。

Stockfishの最新版のmasterもYBWCを捨てて、その新しい並列化のコードになった。そちらのほうがコア数が多いときにパフォーマンスが良いというのだ。当然、Ponanzaも来年はYBWCを捨てるだろう。技巧(2015)は、電王トーナメントの時点でYBWCを採用していなかったと思う。

また、クラスター並列化もYBWCではなくその方法でやったほうがパフォーマンスがいい可能性も(少しだけ)ある。

詳しいことは書ききれないが、ともかく、今後YBWCは使わない&使われないということで間違いない。だからsplitという問題はない。masterとslaveの区別も必要ない。

そのへんを考慮して、やねうら王miniでは上のようなスレッドモデル(slaveとmasterがその区別なしに同じsearch関数を実行する作り)を想定している。

より詳しいことは探索の並列化をするときに書く。
ともかく、やねうら王miniの設計で大丈夫だということだけいまは記しておく。

ここまでのまとめ

・将棋所で対局できるランダムプレイヤーはわずか3行書くだけで出来る!
・並列化対策もやねうら王miniならバッチリ!

次回に続く。


連載やねうら王miniで遊ぼう!10日目” への13件のコメント

  1. >もっといい方法が発明された
    >その新しい並列化のコード
    >詳しいことは書ききれない

    名前だけでも先に教えてもらえませんでしょうか。
    名前がついていないのであれば、キーワードだけでも。。。
    よろしくお願いします。

  2. そう言えば伊藤さんが「Puella α のソースを公開します。画面左上の「資料館」に置きました。使用法等については中のREADMEをご覧ください。」って言ってたこの手法ですか?

    • Puella α、実は見てないんですけど、話聞く感じroot付近のnode展開まとめて展開して、有望そうなnodeをクラスターに振るという、そんな感じのアルゴリズムではないでしたっけ。

      今回のStockfishの並列化アルゴリズムは全然違ってて…

      おっと誰か来たようだ。

  3. 自分はスレッドについてはど素人もいいところで全然触ったことがありません。
    この前スレッドプールを自作してみたんですけど、C++11の壁に挟まれて見事死産しました。
    なんでスレッド走ってるか知るためにブロックするの!!
    グローバル変数は使いたくないのーー!!!
    というわけで研究が進んでいません。
    楽しそうではあるんですけどね。
    こういうことがあるからC++はまだだなーと思います。
    まぁ、怠慢なだけなんですが・・・。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です