やねうら王mini(Stockfish)とAperyの置換表の違い [コンピュータ将棋]
やねうら王miniが公開された。Stockfishベースのところが日本語のコメントで親切に書かれているので大変わかり易い。
で、置換表の話。
やねうら王では以下のようにコメントが書かれている。
これはStockfishと同一だが、Stockfishでは最初からそうだったわけではなく、あるときに32バイトに収まるように変更が入った。
https://github.com/official-stockfish/Stockfish/pull/3
https://github.com/official-stockfish/Stockfish/pull/10
このコミットで、
・確認用のハッシュキーを32ビットから16ビットにして2バイト減らす
・キャッシュの世代管理をする変数generationと探索の確かさであるFail-High/Fail-Lowを保存するboundをまとめてgenBound8にして1バイト減らす
・Depthを16ビットから8ビットにして1バイト減らす。
ということをやって10バイトにした。
この変更は2014年のものだが、Aperyではこの変更が取り込まれていないので試してみたが、これをいれると3秒対戦中にかなりの確率でせぐるようになってしまった。(1秒だとなかなかセグらない)
デバッグ版や1秒対戦だとなかなかでないのだが、-g付きpgoビルドをして、gdbでアタッチできるのがわかったのでセグったところをうまく捕捉できたのだが、要因がさっぱりわからない。
要点は一番上のthisのポインタが0x48という明らかにInvalid Pointerで、それの出処は、moveStackList=moveStackList@entry=0x3ee17958 あたりなのだが、途中でヌルが入る理由がさっぱりわからない。
うまく行けばメモリ効率がかなり良くなるのでPCのメモリがあまり多くない人にも恩恵があるのではないかと思うのだが・・・
<追記>
かなり再現性があって
・ポインタが(たぶん)NULLになるのが要因
・大元のMovePickerの配列legalMove_の要素数を倍にしても関係ない
・search/qsearch中のMovePickerのnextMoveなどでEvasionの手を生成する時に発生する
・発生場所は王手回避のときのみ
Evasionの桂馬、銀、角のどれかで発生する
配列数は600弱もあり、回避するだけの候補手がそんなにでかくなるとも思えないのだが・・・
ここまではわかったが、そもそも置換表のコード入れたらバグったので、置換表のバグでメモリぶっ壊してるのではという気がしないでもない。
<追記>
元々のコードと同等だけどコードが変わるようなのを書いてみたところ、再現率が変わって発生場所も変わったので、やっぱりどっかでメモリを書き潰している可能性が高まった。バックトレースが途中で死亡したのでスタックがぶっ壊れていたりとか、あるので置換表のコードを見直すことにする・・・
で、置換表の話。
やねうら王では以下のようにコメントが書かれている。
// 置換表エントリー
// 本エントリーは10bytesに収まるようになっている。3つのエントリーを並べたときに32bytesに収まるので
// CPUのcache lineに一発で載るというミラクル。
これはStockfishと同一だが、Stockfishでは最初からそうだったわけではなく、あるときに32バイトに収まるように変更が入った。
commit ccd823a4ffd2ed3e60cb03ab49a841742bae1994 Author: Ron BritvichDate: Sat Jun 28 14:05:58 2014 -0400 Pack 3 TT entries in 32 bytes cluster Idea from Ron Britvich Code reworked by Marco Costalba and Joona Kiiski Bench: 8095369 Resolves #3 Resolves #10
https://github.com/official-stockfish/Stockfish/pull/3
https://github.com/official-stockfish/Stockfish/pull/10
このコミットで、
・確認用のハッシュキーを32ビットから16ビットにして2バイト減らす
・キャッシュの世代管理をする変数generationと探索の確かさであるFail-High/Fail-Lowを保存するboundをまとめてgenBound8にして1バイト減らす
・Depthを16ビットから8ビットにして1バイト減らす。
ということをやって10バイトにした。
この変更は2014年のものだが、Aperyではこの変更が取り込まれていないので試してみたが、これをいれると3秒対戦中にかなりの確率でせぐるようになってしまった。(1秒だとなかなかセグらない)
デバッグ版や1秒対戦だとなかなかでないのだが、-g付きpgoビルドをして、gdbでアタッチできるのがわかったのでセグったところをうまく捕捉できたのだが、要因がさっぱりわからない。
0x00000000004337b1 in Move::operator|= (rhs=..., this=0x48) at move.hpp:72 72 this->value_ |= rhs.value(); (gdb) bt #0 0x00000000004337b1 in Move::operator|= (rhs=..., this=0x48) at move.hpp:72 #1 selectedMakeMove<(MoveType)7, (PromoteMode)0> (pos=..., to=SQ11, from=, pt= ) at generateMoves.hpp:66 #2 makePromoteMove<(MoveType)7> (pt= , from= , to=SQ11, pos=...) at generateMoves.hpp:72 #3 0x00000000004fbadd in (anonymous namespace)::GeneratePieceMoves<(MoveType)6, (PieceType)3, (Color)1, false>::operator() (target= , pos=..., moveStackList=0x0, this= ) at generateMoves.cpp:239 #4 (anonymous namespace)::GenerateMoves<(MoveType)6, (Color)1, false>::operator()(MoveStack*, Position const&) [clone .isra.26] () at generateMoves.cpp:468 #5 0x0000000000433555 in generateMoves<(MoveType)6> ( moveStackList=moveStackList@entry=0x3ee17958, pos=...) at generateMoves.cpp:556 #6 0x00000000004f3b5b in MovePicker::goNextPhase() () at movePicker.cpp:239 #7 0x00000000004f34a8 in Move MovePicker::nextMove () () at movePicker.cpp:80
要点は一番上のthisのポインタが0x48という明らかにInvalid Pointerで、それの出処は、moveStackList=moveStackList@entry=0x3ee17958 あたりなのだが、途中でヌルが入る理由がさっぱりわからない。
うまく行けばメモリ効率がかなり良くなるのでPCのメモリがあまり多くない人にも恩恵があるのではないかと思うのだが・・・
<追記>
かなり再現性があって
・ポインタが(たぶん)NULLになるのが要因
・大元のMovePickerの配列legalMove_の要素数を倍にしても関係ない
・search/qsearch中のMovePickerのnextMoveなどでEvasionの手を生成する時に発生する
・発生場所は王手回避のときのみ
Evasionの桂馬、銀、角のどれかで発生する
配列数は600弱もあり、回避するだけの候補手がそんなにでかくなるとも思えないのだが・・・
ここまではわかったが、そもそも置換表のコード入れたらバグったので、置換表のバグでメモリぶっ壊してるのではという気がしないでもない。
<追記>
元々のコードと同等だけどコードが変わるようなのを書いてみたところ、再現率が変わって発生場所も変わったので、やっぱりどっかでメモリを書き潰している可能性が高まった。バックトレースが途中で死亡したのでスタックがぶっ壊れていたりとか、あるので置換表のコードを見直すことにする・・・
コメント 0