GetHashCodeのパフォーマンス
あまり気にしていなかったのですが、プログラミングMS .NET FRAMEWORK 第2版 (マイクロソフト公式解説書)を読んでいたら、
System.ValueTypeのGetHashCodeの実装は、リフレクションを使い(つまり遅い)、型のインスタンスフィールドのいくつかをまとめてXORを取るようになっています。これはかなり荒っぽい実装ですが、型によってはこれでも十分でしょう。
とありました。パフォーマンスが気になったので実験してみることに。
using System; using System.Diagnostics; class CI4 { public int foo; public int bar; public int baz; public int quux; } class CS4 { public string foo; public string bar; public string baz; public string quux; } struct SI4 { public int foo; public int bar; public int baz; public int quux; } struct SS4 { public string foo; public string bar; public string baz; public string quux; } struct S { public int foo; public int bar; public int baz; public int quux; } static class Program { const int LOOP = 10000000; static void Test<T>(T t) { Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < LOOP; ++i) t.GetHashCode(); sw.Stop(); Console.WriteLine (t.GetType().Name + ":" + sw.ElapsedMilliseconds); } static void Main() { Test(new CI4()); Test(new CS4()); Test(new SI4()); Test(new SS4()); Test(""); Test(new string('$', 256)); Test(42); Test(42.0); Test(42.0m); Test(new object()); } } /* CI4:666 CS4:681 SI4:351 SS4:2375 String:59 String:2559 Int32:10 Double:67 Decimal:463 Object:658 */
string参照型は、Object.GetHashCode()を呼び出しているので同じ結果ですね。RuntimeHelper.GetHashCode()あたりを使っているのかしら?、stringは文字列の長さに比例して時間が掛かっていきます。最後にstructですがフィールドが値型だと高速ですが、参照型になると非常に遅くなっているのに気が付きます。structのGetHashCode()が不可解なのでプロファイラで見てみましたが、GetHashCodeの呼び出しが表示されません。ValueType.GetHashCode()が呼ばれると思うのですが、いったいどーなっているやら。
動きが分からないstructをもう少し調べて見ます。
- フィールドを値型と参照型の混在にしてGetHashCode()を呼び出し、ハッシュ値を表示させる。
- 参照型のフィールドの値を書き換えてハッシュ値を表示させると、値は変化なし。
- 値型のフィールドの値を書き換えてハッシュ値を表示させると、値は変化する。
結果だけをみるとstructのハッシュ値は値型のフィールドしか考慮していないように見えます。なのにパフォーマンスに影響が出ているとなると考えられるのがboxingくらいしか思いつきません。ValueType.GetHashCode()のデフォルト実装をこれ以上追ってもあまり意味がないので、取りえあずパフォーマンスの傾向だけ抑えておくことでよしとしました。(^^;