web-dev-qa-db-ja.com

辞書列挙型キーのパフォーマンス

キーに列挙型を使用する一般的な辞書について懸念があります。

以下のページで説明されているように、キーに列挙型を使用するとメモリが割り当てられます: http://blogs.msdn.com/b/shawnhar/archive/2007/07/02/twin-paths-to-garbage-collector -nirvana.aspx

動作をテストして確認しましたが、プロジェクトで問題が発生しています。読みやすくするために、キーに列挙型を使用することは非常に便利だと思います。私にとって最適な解決策は、キーに整数を内部的に使用するIDictionary<TKey, TValue>を実装するクラスを作成することです。その理由は、既存のすべての辞書を変更してキーに整数を使用したり、暗黙的なキャストを行ったりしたくないためです。これはパフォーマンス的には最高ですが、最初は多くの作業が必要になり、読みやすさが低下します。

そこで、GetHashCode(残念ながらメモリを割り当てます)を使用して内部Dictionary<int, TValue>を構築するなど、いくつかのアプローチを試しました。

それで、それを1つの質問にまとめます。 Dictionary<SomeEnum, TValue>のパフォーマンスを維持しながら、Dictionary<int, TValue>の可読性を維持するために使用できるソリューションを誰かが考えることができますか?

どんなアドバイスも大歓迎です。

13

問題はボクシングです。これは、値型をオブジェクトに変換する行為であり、不要な場合と不要な場合があります。

Dictionaryがキーを比較する方法は、基本的に、_EqualComparer<T>.Default_を使用し、GetHashCode()を呼び出して正しいバケットを見つけ、Equalsを呼び出して値があるかどうかを比較することです。私たちが探しているものと同じバケツの中。

良い点はこれです:.NETフレームワークには優れた最適化があり、_"Enum integers"_の場合にボクシングを回避します。 CreateComparer() を参照してください。ここで、整数と列挙型の違いがキーとして表示される可能性はほとんどありません。

ここで注意してください。これは簡単な行為ではありません。実際、深く掘り下げると、この戦いの4分の1はCLRの「ハック」によって実装されているという結論に達します。ここに見られるように:

_   static internal int UnsafeEnumCast<T>(T val) where T : struct    
    {
        // should be return (int) val; but C# does not allow, runtime 
        // does this magically
        // See getILIntrinsicImplementation for how this happens.  
        throw new InvalidOperationException();
    }
_

ジェネリックスにEnum制約があり、おそらくUnsafeEnumCast<T>(T val) where T : Enum->Integerの長い行があれば、間違いなく簡単になる可能性がありますが、そうではありません。

そのEnumCastのgetILIntrinsicImplementationで正確に何が起こっているのか、疑問に思われるかもしれません。私も不思議です。現時点では、確認方法が正確にはわかりません。実行時に、私が信じている特定のILコードに置き換えられますか?!

単核症

今、あなたの質問に答えてください:はい、あなたは正しいです。モノラルのキーとしてのEnumは、タイトなループでは遅くなります。私が見る限り、Monoが列挙型でボクシングを行うためです。ご覧のとおり、 EnumIntEqualityComparer をチェックアウトできます。これは、基本的にTの型を整数にキャストする_Array.UnsafeMov_を呼び出します:_(int)(object) instance;_。これはジェネリックスの「古典的な」制限であり、この問題に対する優れた解決策はありません。

解決策1

具体的な列挙型に_EqualityComparer<MyEnum>_を実装します。これにより、すべてのキャストが回避されます。

_public struct MyEnumCOmparer : IEqualityComparer<MyEnum>
{
    public bool Equals(MyEnum x, MyEnum y)
    {
        return x == y;
    }

    public int GetHashCode(MyEnum obj)
    {
        // you need to do some thinking here,
        return (int)obj;
    }
}
_

次に行う必要があるのは、それをDictionaryに渡すことだけです。

new Dictionary<MyEnum, int>(new MyEnumComparer());

それは機能し、整数の場合と同じパフォーマンスを提供し、ボクシングの問題を回避します。問題は、これは一般的ではなく、Enumごとにこれを書くのはばかげていると感じるかもしれないということです。

解決策2

一般的なEnum比較子を作成し、開封を回避するいくつかのトリックを使用します。私は ここ から少し助けを借りてこれを書きました、

_// todo; check if your TEnum is enum && typeCode == TypeCode.Int
struct FastEnumIntEqualityComparer<TEnum> : IEqualityComparer<TEnum> 
    where TEnum : struct
{
    static class BoxAvoidance
    {
        static readonly Func<TEnum, int> _wrapper;

        public static int ToInt(TEnum enu)
        {
            return _wrapper(enu);
        }

        static BoxAvoidance()
        {
            var p = Expression.Parameter(typeof(TEnum), null);
            var c = Expression.ConvertChecked(p, typeof(int));

            _wrapper = Expression.Lambda<Func<TEnum, int>>(c, p).Compile();
        }
    }

    public bool Equals(TEnum firstEnum, TEnum secondEnum)
    {
        return BoxAvoidance.ToInt(firstEnum) == 
            BoxAvoidance.ToInt(secondEnum);
    }

    public int GetHashCode(TEnum firstEnum)
    {
        return BoxAvoidance.ToInt(firstEnum);
    }
}
_

解決策3

Expression.Compile()はiOSではそれほど有名ではなく(ランタイムコード生成なし)、一部のモノバージョンには??がないため、ソリューション#2には少し問題があります。 _Expression.Compile_ ?? (わからない)。

列挙型変換を処理する単純なILコードを記述し、それをコンパイルできます。

_.Assembly extern mscorlib
{
  .ver 0:0:0:0
}
.Assembly 'enum2int'
{
  .hash algorithm 0x00008004
  .ver  0:0:0:0
}

.class public auto ansi beforefieldinit EnumInt32ToInt
    extends [mscorlib]System.Object
{
    .method public hidebysig static int32  Convert<valuetype 
        .ctor ([mscorlib]System.ValueType) TEnum>(!!TEnum 'value') cil managed
    {
      .maxstack  8
      IL_0000:  ldarg.0
      IL_000b:  ret
    }
} 
_

それをアセンブリにコンパイルするには、以下を呼び出す必要があります。

_ilasm enum2int.il /dll_ここで、enum2int.ilはILを含むテキストファイルです。

これで、指定されたAssembly(_enum2int.dll_)を参照し、静的メソッドを次のように呼び出すことができます。

_struct FastEnumIntEqualityComparer<TEnum> : IEqualityComparer<TEnum> 
    where TEnum : struct
{
    int ToInt(TEnum en)
    {
        return EnumInt32ToInt.Convert(en);
    }

    public bool Equals(TEnum firstEnum, TEnum secondEnum)
    {
        return ToInt(firstEnum) == ToInt(secondEnum);
    }

    public int GetHashCode(TEnum firstEnum)
    {
        return ToInt(firstEnum);
    }
}
_

それはキラーコードのように見えるかもしれませんが、ボクシングを回避し、Monoでより良いパフォーマンスを提供するはずです。

34

私はしばらく前にこの同じ問題に遭遇し、ジェネリック列挙型拡張とヘルパーメソッドについて書いたライブラリに組み込むことになりました(C#では列挙型の型制約の作成が許可されていないため、C++/CLI(コンパイル済みAnyCPU)で書かれています) )。 NuGet および GitHub のApache2.0ライセンスで利用できます。

ライブラリの静的なDictionary型からIEqualityComparerを取得することで、Enumsに実装できます。

var equalityComparer = Enums.EqualityComparer<MyEnum>();
var dictionary = new Dictionary<MyEnum, MyValueType>(equalityComparer);

値は、すでに提供されている回答の1つで言及されているUnsafeEnumCastと同様の手法を使用して、ボックス化せずに処理されます(安全ではないため、テストで死ぬまでカバーされています)。結果として、それは非常に高速です(この場合、それが等式比較器を置き換える唯一のポイントになるため)。ベンチマークアプリと、ビルドPCから生成された最近の結果が含まれています。

1
mdip