Aperyの指し手生成が(MSVCでコンパイルすると)遅いという話がなんとかちゃんねるに書いてあったわけであるが、その件は、やねうら王miniの指し手生成部を書くときに私も気づいたので(私のなかでは)すでに解決済みの問題であった。
Aperyの指し手生成で使われているUnroller、Visual C++ 2015だとlambdaのinliningの最適化が甘くて遅いコードが生成される。 lambdaで書けば綺麗だとかなんとか言われても、最適化も満足にされない状況で使えるわけなくてな…。
— やねうら王 (@yaneuraou) December 29, 2015
ぼくのかんがえたせかいさいきょうのUnroller #define U1(S){const int i=0; S;} #define U2(S){U1(S); const int i=1; S;} #dfeine U3(S){U2(S); const int i=2; S;} …
— やねうら王 (@yaneuraou) December 29, 2015
AperyのUnrollerとは次のようなコードとなっている。
1 2 3 4 5 6 7 8 9 10 11 12 |
// N 回ループを展開させる。t は lambda で書くと良い。 // こんな感じに書くと、lambda がテンプレート引数の数値の分だけ繰り返し生成される。 // Unroller<5>()([&](const int i){std::cout << i << std::endl;}); template template Unroller t(N-1); } }; template <> struct Unroller<0> { template }; |
また、FORCE_INLINEは次のように定義されている。
1 2 3 4 5 6 7 8 9 |
#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) #define FORCE_INLINE __forceinline #elif defined(__INTEL_COMPILER) #define FORCE_INLINE inline #elif defined(__GNUC__) #define FORCE_INLINE __attribute__((always_inline)) inline #else #define FORCE_INLINE inline #endif |
Visual C++で、マクロをラムダ式にしたら遅くなったというのは何回かあったけど、自分じゃなくてコンパイラが悪いんなら堂々とラムダ式を使いたいところではある。あと、forceinlineでカバーできることもある。
— merom686 (@merom686) December 29, 2015
ということで、残念ながらforce_inlineでカバー出来ることではなさそうである。(よくわからない。誰か追試して欲しい。)
そのため、やねうら王miniでは次のマクロを用いている。
1 2 3 4 5 6 7 8 9 |
// --- N回ループを展開するためのマクロ // AperyのUnrollerのtemplateによる実装は模範的なコードなのだが、lambdaで書くと最適化されないケースがあったのでマクロで書く。 #define UNROLLER1(Statement_) { const int i = 0; Statement_; } #define UNROLLER2(Statement_) { UNROLLER1(Statement_); const int i = 1; Statement_;} #define UNROLLER3(Statement_) { UNROLLER2(Statement_); const int i = 2; Statement_;} #define UNROLLER4(Statement_) { UNROLLER3(Statement_); const int i = 3; Statement_;} #define UNROLLER5(Statement_) { UNROLLER4(Statement_); const int i = 4; Statement_;} #define UNROLLER6(Statement_) { UNROLLER5(Statement_); const int i = 5; Statement_;} |
この一件もあって、私のなかではlambdaが信用ならないというか、lambdaをきちんとコンパイルできないMSVC(Visual C++2015)を信用ならないというか、lambdaを使えという人を信用ならないというか、そのいずれもであるというか。
あまりlambdaの悪口を書くと、またC++なんちゃら委員会の人を怒らせてしまいかねないのでまあ、その件はこれくらいにしておく。
それはともかく、なんちゃらちゃんねるにはSDLチェックをオフにすればLinuxと同じぐらいのnpsが出るという書き込みもあった。しかし私が試した限りはSDLチェックをオフにしてもAperyのUnrollerでは指し手生成の速度は改善されなかった。また別の人の書き込みとして、「SDLチェックは/GLオプションをつけた時点で無視されている。」という書き込みもあったが、これは/GLではなく/GS-(セキュリティチェックの無効)のことだろう。
なんとかちゃんねるの情報から正しい情報を掬い上げるのはなかなか大変である。かつてひろゆきは、「うそはうそであると見抜ける人でないと(掲示板を使うのは)難しい」と言ったが、書き込む本人は嘘をついているという自覚がなくとも、本人の勘違いや検証不足・認識不足などにより意図せず間違った内容が書き込まれていることは多々ある。そういった間違った内容の書き込みから正しい情報を掬い上げることが出来ない人には(なんとかちゃんねるを使うのは)難しい。と言えるのかも知れない。
ラムダは、関数を引数に取りたい時にすごい便利なんですよね。
std::functionを引数にしておいてパペットだけ書いておけば汎用性のある関数が書けます。
例えば2分法のロジックとf(x)=0を分離したりとか。
しかし、屋根さんは独自にもうその辺は開発済みでしょうから、あまり恩恵はないかもですね。
天才には時代があとから追いつくもんですよ。
> ラムダは、関数を引数に取りたい時にすごい便利なんですよね。
そんなことは30年以上前から出来ますけどね。#defineで…。
さすがです!!!!
付け加えるんだったら、ランタイムに差し替えれるとか。
#pragma inline_depth(255)
をプログラム先頭に書くと良いようです。なぜかと言えば、__forceinline によって Unroller は確かに unroll されるのですが、Unroller から呼び出されるラムダには __forceinline がついていないため (つける方法もなさそう) 関数呼び出しの深さが深すぎるという判断によりインライン展開してくれないのです。上記プラグマはそれを無効にします。このプラグマをグローバルにするのでなく局所的に適用しようといろいろ試してみたのですが私にはできませんでした
うおおおおお!!素晴らしい!!
再帰せずに unroll を実装するなら↓でしょうか…。これだと #pragma inline_depth なしでもきちんと展開されます (ラムダ本体が大きくなるとどうなるかわかりません)
#include
template
__forceinline
void Unroll_impl_(F&& f, std::integer_sequence)
{
auto wrapper = [i = int(), &f]() mutable { f(i); ++i; };
[](auto…){}((wrapper(), seq)…); // 引数の評価順に依らず f(0),…,f(n-1)が呼ばれる
}
template
__forceinline
void Unroll(F&& f)
{
Unroll_impl_(std::forward(f), std::make_integer_sequence());
}
#include
int main()
{
Unroll([](int i){
std::printf(“ヽ( ・∀・)ノ● %d\n”, i);
});
}
> std::printf(“ヽ( ・∀・)ノ● %d\n”, i);
Unrollでうんこを投げる…。Un、、うんろーるってこと?
すいません、角カッコが消えてしまいました。完全なのはこれです→ http://codepad.org/3wgtRtDf
ここに貼るときは一応う○こを消したのですが codepad では消し忘れました
AperyはSDLチェック外すと指し手生成速度は変わらないけど、NPSは1~2割程度上がりますよ。
やねうら王miniのほうはまだ探索部全く書けてないので、探索部まで書けたときに詳しく調べてみますね。それにしても指し手生成でSDLチェックをオンにしても生成速度が変わらないという事実のほうが私には不思議なんですけど…。