しかし、バイナリコンパチビリティがあるからコンパイルするだけで動きますとは一体…
Cortex-A9のOoOって微妙じゃね?
というのを一年くらい前に調べてた。
https://gist.github.com/1513601
今見て思ったが整数で試したほうがいいな。あとで。
高速化とコードの読みやすさ
高速化するとコード読み辛くなる、という説があるけど、個人的には、決してそんなことは無い、と、思う。
依存関係のわかりやすいコードは、読みやすいし、最適化しやすいし、実際性能もいい。
↑のベンチマークが、何やってるか、という解説は…またそのうち書こうと思うが、こういうことを考えてる時、プログラムを依存グラフの形として考えていて、命令レベルの最適化というのは、この依存グラフの形をCPUにとって見えやすいに変換しているだけで、思考内容としては、コードをきれいに書こうという努力と、かなり同じ思考をしているのだよな。
上の nosched と nosched hazard が良い例だと思うが、
static void __attribute__((noinline,noclone)) func_nosched(data_type *out, data_type *in, int n) { int i; for (i=0; i<n; i+=4) { /* load, mul, store */ out[i+0] = f(in[i+0]); out[i+1] = f(in[i+1]); out[i+2] = f(in[i+2]); out[i+3] = f(in[i+3]); } } /* ... */ // nosched for (i=0; i<nloop; i++) { func_nosched(out, in, ndata); } // nosched hazard for (i=0; i<nloop; i++) { func_nosched(in+1, in, ndata); }
これで、どっちのほうが速いでしょう?というと、
* // E350 * | sched 3100.598877[clk/loop], 3.027929[cyc/data] * | nosched-hazard 14360.824951[clk/loop], 14.024243[cyc/data]
sched hazard のほうが、かなり遅い。(実際には依存があるから公平な比較ではない)
nosched-hazard は何やってるかというと、実際には、
static void __attribute__((noinline,noclone)) func_nosched(data_type *out, data_type *in, int n) { int i; data_type acc = in[0]; for (i=0; i<n; i++) { /* load, mul, store */ in[i+1] = acc; acc = f(acc); } }
こんな感じのことをやっていて、つまり、ループのイテレーションごとに依存がある。
static void __attribute__((noinline,noclone)) func_nosched(data_type *out, data_type *in, int n) { int i; for (i=0; i<n; i+=4) { /* load, mul, store */ out[i+0] = f(in[i+0]); out[i+1] = f(in[i+1]); out[i+2] = f(in[i+2]); out[i+3] = f(in[i+3]); } }
が、それを、このコードから読み取るのは、かなり難しい。
in と、 out が同じ領域を指している、というのは、なんかデバッグしてて、よくわからん挙動をするなー、と思って、バックトレース追いかけて良く見たら同じポインタじゃねーかf**k。みたいな事件は起こりそうである。
多分、↑の説明は、よく読まないとよくわからないと思うが、つまり、そういうことで、人間によくわからないコードと、CPUによくわからないコードは、かなり、似ているということである。
最適化するしないに関わらず、何が、どういう風に依存しているのか、わかりやすいコードを書くべきである。
現代のプログラムは、リファクタリングする時/機能追加する時/並列化する時/コンパイラが自動でスケジュールする時/人間が最適化する時/CPUが命令を実行する時、あらゆる箇所で生産性、性能両方の点でボトルネックになるのは、依存性である。
いや例が悪いな
依存性の見やすさ以外を公平にするなら、
static void __attribute__((noinline,noclone)) loop_dep(volatile data_type *inout, int n) { int i; data_type acc = inout[0]; for (i=0; i<n; i+=4) { /* load, mul, store */ inout[i+0+1] = f(inout[i+0]); inout[i+1+1] = f(inout[i+1]); inout[i+2+1] = f(inout[i+2]); inout[i+3+1] = f(inout[i+3]); } }
と比較すべきで、これなら変わらん。
というわけで上の話は、あんまり真面目に読まないでください。
また良い例を思いついたら書き直そう。
依存性
気がつくとしょっちゅう"依存性"という単語を書いてるが使い方間違ってる印象がある。
多分「依存」とだけ、もしくは文脈によっては「依存関係」とか書くのが正しいのだけど。