df-pnルーチンのあれこれ

df-pnルーチンがようやく完成しました。まだ歩不成みたいな指し手生成はやっていないので、打ち歩詰めが絡む問題は解けませんが、シングルスレッドで200knps程度出ており、試しにかず@なのはさんにいただいた将棋図巧 第2番(21手詰)をやってみたところ2011ノードで解けました。

すなわち、シングルスレッドでおおよそ0.01秒で解けるということですね。

また、詰将棋の解答としては玉側は最長手順で逃げないといけないので、最長手順をdfpnの置換表から引っ張り出してこないといけないのですが、これが結構たいへんでして、いまはそのコードは書いていません。

どうしようかと考え中なのですが、長手数の詰将棋にチャレンジしたり、詰将棋検討エンジン化する気はいまのところあまりないので、こんなの後回しでいいかと思いました。

df-pnの詰将棋ルーチンは長手数の詰みに関しては通常の探索ルーチンより早く詰みが発見できることは間違いのですが、実戦でそんなに長手数のものが出現するはずもなく、通常探索中にdf-pnの詰将棋ルーチンを呼び出すとたいていは時間のロスになります。

時間のロスにならずに、効果的に呼び出せるような条件を見つけ出せれば、それはそれで大変素晴らしいと思うのですが、これはそう簡単な話ではなさそうです。

それで、探索開始時にdf-pn詰将棋ルーチンに40kノード(これなら最大でも0.2秒で終了します。うまくすれば20〜30手詰めぐらいがわかります)だけ割り振るようにして、そうしないバージョンと自己対戦中なのですが、短い持ち時間の将棋ですと、この0.2秒が致命傷になりかねず、どうも勝率がよろしくないです。df-pnルーチン自体は並列化していないことも関係しているのでしょうけども、なんにせよdf-pnの詰将棋ルーチンは別スレッドから呼び出したほうが良さげです。

しかしそう考えますと、実戦的に見てdf-pn詰将棋ルーチンは通常探索にはよほど吟味しない限り組み込めませんし、スレッドを1つか2つ、df-pnの詰将棋ルーチンに割り当てるとしてもその配分が非常に難しく、下手をすると弱くなるだけの結果に終わります。

Bonanzaのようにクラスター化したときにサブのPCではdf-pnで即詰みを調べるというのはアリだと思うのですが、これが強さにどれだけ寄与するのかというのは非常に微妙な話で、そもそも探索ルートで詰みがなければ無用の長物であり、探索ルートで詰みが出現するためには、そこで優勢になっていなければならず、詰みを逃して負けるというパターンが少し防げるだけのように思えます。

そこで、探索ルート以外で必死をかけたり、頓死チェックのためにこのdf-pnの詰将棋ルーチンを活かしたいのですが、これはこれで簡単な話ではありません。簡単なら保木さんがとっくにやっておられるはずです。

詰将棋用のスレッドをひとつ専用に割り当てて、PV(最前応手列)の浅い深さのノードについて、df-pnの詰将棋ルーチンを呼び出して詰みがないか検証するというのはそこそこお手軽に出来る、そこそこよさげな実装方法だと思います。

この場合、PVに出現する頓死筋は防げるようになりますが、必死をかけるのがうまくなるかと言われると微妙なところで、必死をかけるための初手,3手目が捨て駒だったりするとそんな手はPVにはなかなか現れず、そういう形の必死は発見できないであろうことは想像に難くないです。

結局のところ、別スレッドで探索ルートで即詰みを探し続ける程度に落ち着くのですが、2コアで動かしている場合に1コアをそんなことに割り当ててしまいますと、ただ単に弱くなるだけのような気もします。

1コアまるまるを割り当てるわけではなく、そのコアも探索ルートでの不詰めが証明されれば、そのあとは通常探索の並列化に戻るのですが、そうは言ってもdf-pnでなかなか不詰めは証明されません。

結局、「df-pnルーチン書けましたけど、これどうしてくれましょう」というのがいまの私の気持ちです。

Idleになったスレッドはイベント待ちにする件

2chのコンピューター将棋スレでいただいた質問にお答えします。

スレッドの生成に時間がかかる、というのはわかるが
スレッドを待たせるのにCPU時間を使う理由がわかんね
Idleになったスレッドは(終了せずに)イベント待ちにして、
必要なとき外からSetEvent()して起こす、じゃいかんの?

まず、スレッドを待たせるのにCPU時間を使っていた理由ですが、Bonanzaではスレッドの待機にWindows系ではSleep(0)を用いており(thread.cのwait_work関数)、Sleep(0) → 仕事があるかどうかを見る → Sleep(0)→… というspinlock構造になっています。

Bonanzaでは自分の手番が終わったときにスレッドはいったん終了させているのですが、私はスレッド起動に要する時間が惜しかったのでそのまま待機させていました。そうするとSleep(0)をspinlockで繰り返すためにCPU時間をいくぶん消費していたということです。

このときの(相手番での)待ちかたとしてイベント待ちにするのはそれが普通だと私は思います。

しかしSleep(0)までイベント待ち(WaitForSingleObject)に置き換えた場合、この待ち状態からの復帰にかかる時間はおそらくSleep(0)からの復帰時間より長いと思うので[要検証]、Sleep(0)までは置き換えられません。

また、スレッド分だけイベントを作成して、スレッド数の変更を指定されたときにまたイベントを解体して作成しなおしたりするコードを書くのが面倒でしたので、私は

    if ( is_tlp_sleep )
        Sleep(1);

とstaticなbooleanなフラグをひとつ用意してSleep(1)を使って解決しました。 あまりお行儀のいいコードではありませんが。

df-pn 終了のお知らせ

そんなわけでバックグラウンドで(スレッド1つだけで)探索開始局面でdf-pnの詰将棋ルーチンを呼び出すだけならもしかしたら意味があるかも知れないと思い、今日、苦労して実装しました。df-pnで不詰めが証明されれば、通常探索の並列用スレッドとしての役割を果たすというなかなかの優れものです。

ところが、そのdf-pn付きのひよこカルロ将棋neoと、df-pnなしのひよこカルロ将棋neoとを対戦させてみましたところ、持ち時間5分だと後者が8割ぐらい勝ち越すのです。

バグっているのかさんざん検証してみましたが、どうやらこれが正常動作のようです。

この理由について、ざっと説明しますと、まず、df-pnの詰将棋ルーチン、置換表を参照する回数が非常に多いのでメモリの転送帯域の大半をこれで消費しているようです。そのため通常探索のときに置換表にアクセスするのが遅れ、それゆえ通常探索のnpsが若干低下するようです。

この若干というのが、ひよこカルロ将棋neoにとっては致命的でして、評価関数は駒得しか見てませんから、わずかにでも読みが上回ったほうが大きく勝ち越すという性質があるようです。

そこで、df-pn搭載→通常探索のnps低下→読み負けというコンボのようです。
即詰みのある局面に到達する以前に敗勢になっているのでdf-pnの出番がありません。

おまけに、ひよこカルロ将棋はもともとnpsが高く、20手ぐらいの詰将棋は普通の探索時にでも読めなくはない範疇です。ということはdf-pnがあったおかげで即詰みに討ち取れるということは極めて稀で、勝率にほとんど影響しません。

ゆえに、ひよこカルロ将棋neoとdf-pnは極めて相性が悪いということが言えると思います。

裏を返せばこれが、
・通常探索時に置換表にあまりアクセスしないタイプの将棋ソフト
・評価関数が優秀でnpsが少し低下しても強さにあまり響かない将棋ソフト
・nps自体は低く、あまり深くまでは読めていないので20手ぐらいの即詰みを発見しにくいソフト
であればdf-pnの詰将棋ルーチンをバックグラウンドで動かす価値はあるのかも知れません。

GPS将棋とかBonanzaとかは、おそらくそういうタイプに分類されるのでしょうね。

逆に言えばひよこカルロ将棋neoには無用の長物でしたという実験結果に終わりました。

丸3日ぐらいかけて苦労して実装したdf-pnがひよこカルロ将棋neoには使えないということがわかりました。今日も枕を濡らしながら寝ます。みなさん、おやすみなさい。