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をもう少し調べて見ます。

  1. フィールドを値型と参照型の混在にしてGetHashCode()を呼び出し、ハッシュ値を表示させる。
  2. 参照型のフィールドの値を書き換えてハッシュ値を表示させると、値は変化なし。
  3. 値型のフィールドの値を書き換えてハッシュ値を表示させると、値は変化する。

結果だけをみるとstructのハッシュ値は値型のフィールドしか考慮していないように見えます。なのにパフォーマンスに影響が出ているとなると考えられるのがboxingくらいしか思いつきません。ValueType.GetHashCode()のデフォルト実装をこれ以上追ってもあまり意味がないので、取りえあずパフォーマンスの傾向だけ抑えておくことでよしとしました。(^^;