10

本当に簡単な質問です。私は、一種のコンテキスト ストレージからプロパティ値を動的に保存および取得する必要があるプロジェクトに取り組んでいます。値は時々書き込まれ、複数回読み取られます。ここでは、取得速度が最優先事項であり、ナノ秒単位が重要です。

通常、これを Dictionary で実装するだけですが、C# 4 と ExpandoObject を使用すると、もっと良い方法があるのではないかと考えています。誰もそれを経験したことがありますか?辞書を使用して実装されていないことを他の投稿で見たことがありますが、それが速いか遅いかについて興味がありますか?

疑似コードで明確にしてみましょう。

// In the main loop
var context = new Context();
context["MyKey"] = 123;
context["MyOtherKey"] = "CODE";
context["MyList"] = new List<int>() { 1, 12, 14 };

foreach(var handler in handlers) {
    handler.DoStuff(context);
}

-

// "Handlers"
class MyFirstHandler {
     void DoStuff(Context context) {
          if (context["MyKey"] > 100)
               context["NewKey"] = "CODE2";
     }
}

class MySecondHandler {
     void DoStuff(Context context) {
          if (context["MyOtherKey"] == "CODE")
             context["MyList"].Add(25); // Remember, it's only Pseudo-code..
     }
}

さて、うまくいけば、あなたは私がやろうとしていることを理解する..

私はここで他の提案にも完全にオープンです。私は、Context クラスを静的に型付けする (つまり、実際にMyKeyプロパティやMyOtherKeyプロパティなどを持つ) というアイデアをいじっていました。

4

3 に答える 3

10

ここでは検索の速度が最優先事項であり、ナノ秒ごとが重要です。

関係することはdynamicおそらくあなたが探しているものではありません...

誤解しないでください。かなり最適化されていますが、基本的に文字列間の辞書検索が必要な場合は、辞書を使用してください。

または、キーの数が限られている場合は、キーとして列挙型またはint定数の束を含む配列を使用することを検討しましたか?

于 2010-08-19T14:20:26.357 に答える
3

文字列のリストが事前にわかっている場合は、IL Emit を使用して、検索文字列の文字に基づいて分岐ツリーを作成し、配列へのインデックスに解決できます。これにより、検索速度がかなり速くなります。

IL Emit を学んでいる間、私は楽しみと練習のためにこのようなものを実装しました。私が試した限られたテスト ケースに基づいて動作しますが、より堅牢にして、運用コード用の適切な単体テストを作成する必要があることは間違いありません。生のコードを投稿しました (少し長いです)。特定のケースではいくつか変更する必要がありますが、コア ロジックはそこにあります。ヘルパー関数は入れませんでしたがEmitLdc(オーバーロードが多い)、任意の定数をスタックにロードする関数です。Ldstr と Ldc_I4 をそれぞれ使用して、文字列型と数値型を直接発行する呼び出しを簡単に置き換えることができます。

    protected void GenerateNestedStringSearch<T>(ILGenerator gen, T[] values, Func<T, string> getName, Action<ILGenerator, T> loadValue)
    {
        //We'll jump here if no match found
        Label notFound = gen.DefineLabel();

        //Try to match the string
        GenerateNestedStringSearch(gen, notFound, values, getName, loadValue, 0);

        //Nothing found, so don't need string anymore
        gen.MarkLabel(notFound);
        gen.Emit(OpCodes.Pop);

        //Throw ArgumentOutOfRangeException to indicate not found
        gen.EmitLdc("name");
        gen.EmitLdc("Binding does not contain a tag with the specified name: ");
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Call, typeof(String).GetMethod("Concat",
                                                        BindingFlags.Static | BindingFlags.Public,
                                                        null,
                                                        new[] { typeof(string), typeof(string) },
                                                        null));
        gen.Emit(OpCodes.Newobj,
                 typeof(ArgumentOutOfRangeException).GetConstructor(new[] { typeof(string), typeof(string) }));
        gen.Emit(OpCodes.Throw);
    }

    protected void GenerateNestedStringSearch<T>(ILGenerator gen, Label notFound, T[] values, Func<T, string> getName, Action<ILGenerator, T> loadValue, int charIndex)
    {
        //Load the character from the candidate string for comparison
        gen.Emit(OpCodes.Dup);
        gen.EmitLdc(charIndex);
        gen.Emit(OpCodes.Ldelem_U2);

        //Group possible strings by their character at this index
        //We ignore strings that are too short
        var strings = values.Select(getName).ToArray();
        var stringsByChar =
            from x in strings
            where charIndex < x.Length
            group x by x[charIndex]
                into g
                select new { FirstChar = g.Key, Strings = g };

        foreach (var grouped in stringsByChar)
        {
            //Compare source character to group character and jump ahead if it doesn't match
            Label charNotMatch = gen.DefineLabel();
            gen.Emit(OpCodes.Dup);
            gen.EmitLdc(grouped.FirstChar);
            gen.Emit(OpCodes.Bne_Un, charNotMatch);

            //If there is only one string in this group, we've found our match
            int count = grouped.Strings.Count();
            Debug.Assert(count > 0);
            if (count == 1)
            {
                //Don't need the source character or string anymore
                gen.Emit(OpCodes.Pop);
                gen.Emit(OpCodes.Pop);

                //Return the value for this name
                int index = Array.FindIndex(strings, s => s == grouped.Strings.First());
                loadValue(gen, values[index]);
                gen.Emit(OpCodes.Ret);
            }
            else
            {
                //Don't need character anymore
                gen.Emit(OpCodes.Pop);

                //If there is a string that ends at this character
                string endString = grouped.Strings.FirstOrDefault(s => s.Length == (charIndex + 1));
                if (endString != null)
                {
                    //Get string length
                    gen.Emit(OpCodes.Dup);
                    gen.Emit(OpCodes.Call, typeof(char[]).GetProperty("Length").GetGetMethod());

                    //If string length matches ending string
                    gen.EmitLdc(endString.Length);
                    Label keepSearching = gen.DefineLabel();
                    gen.Emit(OpCodes.Bne_Un, keepSearching);

                    //Don't need the source string anymore
                    gen.Emit(OpCodes.Pop);

                    //Create an UnboundTag for this index
                    int index = Array.FindIndex(strings, s => s == endString);
                    loadValue(gen, values[index]);
                    gen.Emit(OpCodes.Ret);

                    //String length didn't match
                    gen.MarkLabel(keepSearching);
                }

                //Need to consider strings starting with next character
                var nextValues = from s in grouped.Strings
                                 join v in values on s equals getName(v) 
                                 select v;

                GenerateNestedStringSearch(gen, notFound, nextValues.ToArray(),
                    getName, loadValue, charIndex + 1);
            }

            //This character didn't match, so consider next character
            gen.MarkLabel(charNotMatch);
        }

        //We don't need the character anymore
        gen.Emit(OpCodes.Pop);

        //No string match, so jump to Not Found at end of check
        gen.Emit(OpCodes.Br, notFound);
    }

編集:実際には文字列キーを使用していないことに気付いたので、これはあなたのケースには当てはまらないかもしれません. それらを使用する前に、必要なすべてのキーをまとめて収集できる限り、他のルックアップで同様の手法を使用できます。他の誰かが役に立つかもしれない場合に備えて、これをここに保持します。

于 2010-08-19T20:33:32.767 に答える
2

最初の呼び出しでそれほど速くする必要がありますか? 呼び出しサイト キャッシュのおかげで、動的オブジェクト用に作成された式ツリー (追加したメソッドを含む) は、コンパイル後にキャッシュされ、再度使用すると返されます。

ExpandoObject を使用するとうまくいくはずですが、絶対に最高のパフォーマンスを得る必要がある場合は、カスタム型を使用する必要があります。

于 2010-08-19T20:02:15.167 に答える