パフォーマンス - foreach って大丈夫?
最初に軽くid:ladybug:20050804#p1 の http://www.microsoft.com/japan/msdn/enterprise/pag/scalenet-intro.asp について触れておくと、
とりあえず、日本語になっている Part I & II を眺めてみると、この2章は .NET に固有の内容よりも、まずは考え方やらなんやらがメインといったところでしょうか。やっぱり、これ読んでないかも。
パフォーマンス問題に関しては、実務上でいろいろとやってる都合で経験的に直感している部分が大きいのでまじめに勉強する時間があれば勉強してみたいところですね。
たとえば
これは、C# でプログラムを組むようになって一ヶ月もたたないうちに思ったことなのですが、構造体(値型)のボックス化が自動であることを学んだ上で、それを保持する配列やコレクションの中身に対して処理を実行する場合、
// (1) by index for (int i = 0; i < container_count; i++) { // access by 'container[i]' }
// (2) auto enumeration foreach (VALUE value in container) { // access by 'value' }
// (3) manualy enumeration using (IEnumerator e = container.GetEnumerator()) { while (e.MoveNext()) { VALUE value = (VALUE) e.Current; // access by 'value' } }
といった感じに記述が可能なわけですが、現状のコンパイラ(C# と JIT)は、どれぐらいこれを最適化できるんだろうか?とか、ふと気にしませんか?
JIT コンパイラの上で、ループが減算になるか?とか、JIT 後のネイティブコードの格納アドレスの連続性を確保してジャンプ等の最適化もやってるか?とか、そんなレベルことを考える以前に、たとえば値型の配列型(Array)に対して (3) のコードを動かすと、ボックス化とアンボックス化が Current のアクセスのたびに発生してめちゃくちゃ遅いんじゃないだろうか、とか気になったりしませんか?
さらに foreach は IL のレベルには存在しない C# コンパイラが扱う構文だから、(2) をコンパイルしたら (3) とほぼ同じ IL に展開されて、(2) も (3) と同じ理由で遅くなるんじゃないだろうか!? とか怖くなってきませんか?*1
とりあえずこの内容に関しては、現状の Microsoft Visual C# .NET Compiler 7.10.6001.4 は (2) の例で container にあたる部分の型によって foreach の IL を (1) と (3) のどちらに展開するか決定しているようです。int[] を渡すと (1) になり、ArrayList を渡すと (3) になる、というようなかんじです。*2IList 型を (1) に展開できる仕組みもちょっと欲しいと思ったりします。*3
.NET 2.0 では IEnumerator`1 を利用することで (1) のタイプのコードを生成しなくともボックス化の影響を受けないようにすることも可能になりますが、現状の .NET 1.1 ではプロファイラや実測を見るかぎり ArrayList あたりは (3) よりも (1) が高速なわけで、0.1ms ぐらいのオーダーを削る時には気にしたい項目かもしれません。