yaneurao / YaneuraOu

YaneuraOu is the World's Strongest Shogi engine(AI player) , WCSC29 1st winner , educational and USI compliant engine.
GNU General Public License v3.0
512 stars 140 forks source link

MSB64 を使うことで香車の効きの最適化 #272

Closed grafi-tt closed 6 months ago

grafi-tt commented 6 months ago

ビットボードの実装を眺めていて、少し最適化を試してみました。

メインの変更は一つ目のコミットで、perft を試してみて Linux / CPU Zen2 / Compiler clang18 の環境で 7% くらい高速化しました。先手の香車の効きを求めるところで、1 を下位ビット側にビットシフト複数回で伝搬させる代わりに、MSB64 を使って最上位に立っている 1 の場所を求める実装としました。

こういう処理は本質的に x86 における bsr や lzcnt 命令を使うか、ビットシフトを log(bitwidth) 回行う必要があるはずです。Zen4 より前では では bsr のコストが高いという理由からか、lzcnt 命令にコンパイルされるようでした。命令数としては bsr の方が得にはなるのでコンパイラオプションとして -mno-lzcnt つけるとか、処理をいじって lzcnt 向けに最適化するとかやってみたのですが、速くなる感じはなかったので、コードのシンプルさなども考えて今の比較的わかりやすい書き方にしました。

AgnerFog https://www.agner.org/optimize/instruction_tables.pdf を見る限り AMD 系だと lzcnt のレイテンシが 1 で、Intel 系だと 3 のようなので、Intel 系では僅かにしか速くなっていないとは思います。

具体的には

// old
mocc |= mocc >> 1;
mocc |= mocc >> 2;
mocc |= mocc >> 4;
mocc >>= 1;

// new
// `MSB64(x`) is `lzcnt(x) ^ 63` or `bsr(x)`
mocc = ~uint64_t{0} << MSB64(mocc | 1);

の比較ということになりますが、前者のレイテンシは 7 cycles で、後者のレイテンシは Intel だと lzcnt で 6 cycles、bsr で 5 cycles となります。

また、128bit のデクリメントの最適化もやってみました。さらに unpack をなくしてみるのも試したのですが、やっぱり unpack した状態でデクリメントする現状の実装のほうが速そうなので諦めました。

yaneurao commented 6 months ago

プルリクありがとうございます。素晴らしいです!

当該部分の香の利きのコード、私は書いてる時は「このあと55将棋と56将棋の対応コード書かないといけないから、わかりやすいコードにしよう…」と思って詰めきれてませんでした。