http://robert.ocallahan.org/2017/04/rust-optimizations-that-c-cant-do_5.html これを読んで、なるほどRustだとこういう最適化できるのね、というのを学んだ。
Here it’s really clear that the semantics of Rust are making this optimization possible. In C++ v could be a reference to a global variable which is modified by the callback function, in which case hoisting the load would be incorrect.
とあるようにC++だとvがグローバル変数への参照として渡ってきて、callbackの中でそのグローバル変数の書き換えが起こるかもしれない。 なんで、 [qword ptr [r15]] って感じでメモリをloadしにいく必要がある。(下にあるLink-Time Optimizationうんぬんは無視する)。 まあそのへんはsharedを型に使ってるD言語、LDCあたりだとどうなるんだろう(まあなんとなくC++と同じ気はする、グローバル変数触らないという制約はもてない気する)と思ってざっとみてみる。
こういうコードを書く。
ulong foo(ref ulong v, void function() callback) { auto sum = 0; foreach (_; 0 .. 100) { sum += v; callback(); } return sum; }
ldc2 -O -c -ofldc.o hoge.d でコンパイルした結果:
... 20: 41 03 1e add (%r14),%ebx 23: 41 ff d7 callq *%r15 26: 48 63 db movslq %ebx,%rbx 29: ff cd dec %ebp 2b: 75 f3 jne 20 <_D4hoge3fooFKmPFZvZm+0x20>
ループ内部はこんな感じになっていて、まあ期待(?)した結果になった。Intel記法だとdword ptrとかだしてくれるんでそっちしたほうがよかったかもしれない。
ただRust側で addとcallが並び変わってる理由がよくわかってない。
追記
pure
を使えばいけるかも、と思ったが
$ ldc2 -O -c -ofldc.o hoge.d hoge.d(7): Error: pure function 'hoge.foo' cannot call impure function pointer 'callback'
のように怒られてしまった。
ちょっと書き換えてみる。
ulong foo(ref ulong v, int function() pure nothrow @safe @nogc callback) { auto sum = 0; foreach (_; 0 .. 100) { sum += v; callback(); } return sum; }
それでもだめなようだ
$ ldc2 -O -c -ofldc.o hoge.d $ objdump -Mintel -Cd ldc.o # Intel記法にした ... 20: 41 03 1e add ebx,DWORD PTR [r14] 23: 41 ff d7 call r15 26: 48 63 db movsxd rbx,ebx 29: ff cd dec ebp 2b: 75 f3 jne 20 <_D4hoge3fooFKmPFNaNbNiNfZiZm+0x20>