やねうら王miniのソースコードを実際にいじって、盤面を操作したりして遊ぼう!という連載記事です。
※ やねうら王miniのソースコードは年末までにGitHubで公開する予定です。
今回は環境設定からビルド手順まで。
連載の目的
将棋の合法手生成だけでも初めて書くととても大変で、上位のソフト、例えば、Apery並の速度にしようとすると困難を極めるわけである。
こういう部分が、将棋ソフトの開発を難しくてしている一因であり、お手軽に使える高速な指し手生成ルーチン、お手軽に使える(自分のソフトに組み込める)Bonanzaのような三駒の評価関数が必要とされている。
やねうら王miniのソースコードはその基盤となるソフトウェアライブラリとして十分な性能と手軽さ、ソースコードの短さ、わかりやすさを兼ね備えている。
そこで、やねうら王miniのソースコードを利用して、独自の将棋の思考エンジン、もしくは将棋に関する何かのプログラムを作ることを想定し、そのための方法を解説するのが本連載の目的である。
ビルドについて
やねうら王miniは、Visual C++ 2015のプロジェクトファイルを開いて一発でビルドできる。Visual Studio 2015 Express、もしくはVisual Studio Community 2015は無償で入手できる。入手方法はggrks(ググレカス)である。
ソースコードをGitHubから
GitHubからソースコードを取ってくる方法?それもggrksである。
※ やねうら王miniのGitHubのアドレスは公開後にここに追記する。
ターゲットCPUの選択
shogi.hの以下の部分をコメントアウトすれば、AVX2命令を使わない実行ファイル、SSE4.2命令を使わない実行ファイルを作ることが出来る。(AVX2命令が使えるCPUならSSE4.2は使えるはずだ。)
※ shogi.h は types.h と config.hになりました。[2019/05/23]
1 2 3 4 5 6 7 8 9 10 11 |
// --- 以下、ターゲットCPUの選択 // AVX2(Haswell以降)でサポートされた命令を使うか。 // このシンボルをdefineしなければ、pext命令をソフトウェアでエミュレートする。 // 古いCPUのPCで開発をしたていて、遅くてもいいからともかく動いて欲しいときにそうすると良い。 #define USE_AVX2 // SSE4.2以降でサポートされた命令を使うか。 // このシンボルをdefineしなければ、popcnt命令をソフトウェアでエミュレートする。 // 古いCPUのPCで開発をしたていて、遅くてもいいからともかく動いて欲しいときにそうすると良い。 #define USE_SSE42 |
あとVisual C++ 2015では、やねうら王miniのプロジェクトの設定のところでターゲットCPUがAVX2になっているところを変更しないといけないかも知れない。
※ プロジェクトの [プロパティ ページ]→[構成プロパティ]、[C/C++]→[コード生成]→[拡張命令セットを有効にする]→[Advanced Vector Extensions 2 (/arch:AVX2)]をお使いのCPUに合わせて変更。詳しくはggrksである。
古いCPUの対応について
本来なら、Haswellのpextを使えないなら、pextを使わずにそこそこの速さで動くコードも用意すべきである。Aperyではpextを使わずに遠方駒(香・角・飛)の利きを求めるコードには、magic bitboardが用いられていて、pextが使えない環境でもほとんど速度低下をさせずに動作させることが出来る。それは作者の平岡さんの大変な努力の賜物ではあるが(もともとAperyではmagic bitboardを使って実装してあって、そのあとpextを使う実装を追加したから結果的に両対応になっているとも言える)、やねうら王miniの場合は、新規に開発しているので、近い将来、市場から無くなっていくCPUのためにそこまでサポートしていられないのが実情である。
そこで、別の実装を用意するのではなく、pext命令やpopcnt命令自体をエミュレーションするコードを書くことにした。
…というと格好いいかも知れないが、以下のようにすこぶる遅い実装にならざるを得ない。(このコードを理解する必要はない。興味のある人向け。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#ifdef USE_AVX2 // for SSE,AVX,AVX2 #include // for AVX2 : hardwareによるpext実装 #define PEXT32(a,b) _pext_u32(a,b) #define PEXT64(a,b) _pext_u64(a,b) #else // for non-AVX2 : software emulationによるpext実装(すこぶる遅い。とりあえず動くというだけ。) template { T res = 0; size_t bitn = 0; for (size_t bit = 0; bit != sizeof(T) * 8; ++bit) if ((mask >> bit) & 1) res |= static_cast return res; } inline uint32_t PEXT32(uint32_t a, uint32_t b) { return pext inline uint64_t PEXT64(uint64_t a, uint64_t b) { return pext #endif |
その代わり、ソースコードのなかでpextをいくら使っても都度古いCPUのためにpextを使わない実装を用意する必要がない。これはこれで現代風の解決法であると思う。
とは言え、popcnt命令のエミュレーションコードについてはさらにひどい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#ifdef USE_SSE42 // for SSE4.2 #include #define POPCNT8(a) __popcnt8(a) #define POPCNT32(a) __popcnt32(a) #define POPCNT64(a) __popcnt64(a) #else // software emulationによるpopcnt(すこぶる遅い) inline int32_t POPCNT8(uint32_t a) { int32_t c = 0; for (int i = 0; i < 8; ++i) c += (a >> i) & 1; return c; } inline int32_t POPCNT32(uint32_t a) { int32_t c = 0; for (int i = 0; i < 32; ++i) c += (a >> i) & 1; return c; } inline int32_t POPCNT64(uint64_t a) { int32_t c = 0; for (int i = 0; i < 64; ++i) c += (a >> i) & 1; return c; } #endif |
popcnt命令は、ある整数型の変数をbit列とみなしたときに1であるbitを数える命令であるから、もうちょっとマシな実装をしようと思えば古来より伝わる秘伝を用いれば、もちろん色々と出来るわけであるが、結局、古いCPUではとりあえず動けば良いと割りきってある。詳しく知りたい人は”counting 1bits”でggrksである。
追記
コメント欄でpextのもう少しいい実装を教えていただいたので次のようにしてみました。POPCNTも、コメント欄にいただいたコードに差し替えました。ハードウェアで実装するよりは全然遅いですけど、ソフトウェア実装としてはかなり良くなりましたということで…。
1 2 3 4 5 6 7 8 9 10 11 |
// for non-AVX2 : software emulationによるpext実装(やや遅い。とりあえず動くというだけ。) template { T res = 0; for (T bb = 1; mask; bb += bb) { if (val & mask & -mask) res |= bb; mask &= mask - 1; } return res; } |
次回に続く。
USE_AVXの部分は、AVXが使えてもPEXTは使えないCPUもあるので、USE_AVX2またはUSE_BMI2としたほうが良いと思います。
ああ、そうか…。修正しときます(`・ω・´)ゞ
>入手方法はggrks(ググレカス)である。
何というキタナイコマンドなんでしょう!!
現代の匠(たくみ)だけが使える魔法のコマンドでありましょう。
匠コトバ? 本当ですか?
何やら某巨大掲示板生まれの様なにおいがしますが、、、。
公開されたら、新しいPC買います^^。
古いPCでも一応は動きます!プログラムいじるだけなら古いPCでも全然問題ないと思います。強さはどうせAperyのほうがはるかに強いですし…。
今思ってる改造の方向性はNDFですね。
基礎能力は屋根さんのほうははるかに上なのでそこをいじらず、サブの部分を改造するところから初めてどれくらい強くなるか。っていうのを妄想しています。
まぁ、読めればですけど。
ちなみに、最近PC買い換えてCPUがスカイレークになりました。ドンとこいデス。
すかいらーくに空目。
あるあるネタですね!
「自分も将棋ソフト作ってみたい…
でもプログラミング全然ワカンネ。おやすみー」と思ってる人間は自分だけじゃないはず…
コンピュータ将棋を身近なものにしてくれそうなこの連載、非常に楽しみにしてます。
プログラミング自体は、並行して勉強しませんと…。
やねうら王mini、とても楽しみにしております。
Bonanzaより強いとなると、やねうらおさんほどの方でも5000行くらいは行くような気がしています。個人的には、ぜひ学習部を解説してほしいと思っております。
pextの自前実装については、私が調べた限りでは、下で紹介されているコードがもっともスマートかつ高速だと思います。(あまりこのあたりにはこだわっていないのかも知れませんが)
https://chessprogramming.wikispaces.com/BMI2
すでにご存知かもしれませんが、参考になれば幸いです。
& -maskね!それ考えた人、賢すぎ!!使わせてもらいます(`・ω・´)ゞ
流石にpopulation countをループで解決するのはアレなので大体こんな感じになるんですかね。
inline int32_t POPCNT8(uint32_t a) {
a = (a & UINT32_C(0x55)) + (a >> 1 & UINT32_C(0x55));
a = (a & UINT32_C(0x33)) + (a >> 2 & UINT32_C(0x33));
a = (a & UINT32_C(0x0f)) + (a >> 4 & UINT32_C(0x0f));
return (int32_t)a;
}
inline int32_t POPCNT32(uint32_t a) {
a = (a & UINT32_C(0x55555555)) + (a >> 1 & UINT32_C(0x55555555));
a = (a & UINT32_C(0x33333333)) + (a >> 2 & UINT32_C(0x33333333));
a = (a & UINT32_C(0x0f0f0f0f)) + (a >> 4 & UINT32_C(0x0f0f0f0f));
a = (a & UINT32_C(0x00ff00ff)) + (a >> 8 & UINT32_C(0x00ff00ff));
a = (a & UINT32_C(0x0000ffff)) + (a >>16 & UINT32_C(0x0000ffff));
return (int32_t)a;
}
inline int32_t POPCNT64(uint64_t a) {
a = (a & UINT64_C(0x5555555555555555)) + (a >> 1 & UINT64_C(0x5555555555555555));
a = (a & UINT64_C(0x3333333333333333)) + (a >> 2 & UINT64_C(0x3333333333333333));
a = (a & UINT64_C(0x0f0f0f0f0f0f0f0f)) + (a >> 4 & UINT64_C(0x0f0f0f0f0f0f0f0f));
a = (a & UINT64_C(0x00ff00ff00ff00ff)) + (a >> 8 & UINT64_C(0x00ff00ff00ff00ff));
a = (a & UINT64_C(0x0000ffff0000ffff)) + (a >>16 & UINT64_C(0x0000ffff0000ffff));
return (int32_t)a + (int32_t)(a>>32);
}
はい、それが普通でしょうね。よくあるcount 1 bitsのコードですね。POPCNT自体はほとんどソース上で使ってないので影響軽微&ソースコード長くなる&古いCPUはとりあえず動けばいいかぐらいの感じなのでいまのコードにしましたけど、せっかくなので使わせてもらいます!(`・ω・´)ゞ
スマホ(Android端末)でC++のアプリを使ってビルドすることは可能ですか
「C++のアプリ」が何を指すのかわかりませんけども、やねうら王自体は、Android用にビルドできるようになってます。(そういう意味ではない?)
まだ私15歳なのでぜんぜんわかんなくて、、、
テスト終わったらコンピューター将棋やC++勉強します‼️