2010-08-19 11 views
7

Eine ziemlich einfache Frage wirklich. Ich arbeite an einem Projekt, bei dem ich Eigenschaftswerte dynamisch aus einer Art Kontextspeicher speichern und abrufen muss. Die Werte werden hin und wieder geschrieben und mehrmals gelesen. Die Geschwindigkeit des Abrufs hat hier oberste Priorität, und jede Nanosekunde zählt.ExpandoObject vs. Wörterbuch aus Sicht der Leistung?

Normalerweise würde ich das einfach mit einem Dictionary implementieren, aber mit C# 4 und dem ExpandoObject denke ich, dass es vielleicht einen besseren Weg gibt? Hat jemand Erfahrung damit? Ich habe in anderen Posts gesehen, dass es NICHT mit einem Dictionary implementiert wird, was mich neugierig macht, ob es schneller oder langsamer ist?

Lassen Sie mich versuchen, mit einigen Pseudo-Code zu klären:

// 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.. 
    } 
} 

Na, hoffentlich bekommen Sie, was ich versuche ich zu tun ..

bin auch hier völlig offen für andere Vorschläge. Ich habe mit der Idee gespielt, die Context-Klasse statisch typisiert zu machen (d. H. Tatsächlich eine MyKey Eigenschaft, eine MyOtherKey Eigenschaft usw.), und obwohl es möglich wäre, würde es die Produktivität für uns ziemlich behindern.

+0

Es sieht so aus, als ob Sie viele String-Konstanten haben. Bedeutet dies, dass Sie im Voraus eine Liste Ihrer Dictionary Keys haben? Wenn ja, verwenden Sie einfach eine Vanilla-Klasse, um den Overhead Ihrer Hash-Funktion zu vermeiden. – Juliet

+0

Ja, ich habe tatsächlich nur die String-Konstanten verwendet, um den Pseudocode zu vereinfachen. Im "aktuellen" realen Beispiel verwende ich schnellere Schlüssel. – CodingInsomnia

+0

[Hier ist ein einfacher Vergleich] (http://spiritofdev.blogspot.in/2011/12/performance-of-c-40-dynamic-vs.html). Wenn Sie die Konstruktionskosten ignorieren können, sollten die Lookups ähnlich funktionieren. – nawfal

Antwort

6

Die Geschwindigkeit des Abrufens hat hier oberste Priorität, und jede Nanosekunde zählt.

Alles, was mit dynamic zu tun, wahrscheinlich ist nicht, was Sie dann freuen ...

Versteh mich nicht falsch, es ist ziemlich stark optimiert - aber wenn man grundsätzlich nur möchte ein String-zu-String-Wörterbuch Lookup, Stock mit einem Wörterbuch.

Alternativ, wenn Sie eine begrenzte Anzahl von Schlüsseln haben, haben Sie in Betracht gezogen, nur ein Array mit einer Enum oder einer Reihe von int Konstanten als Schlüssel?

+0

Danke, das war irgendwie, was ich erwartet hatte (aber nicht das, was ich mir erhofft hatte). Ich denke, das Problem mit einem einfachen Array wäre so ziemlich dasselbe wie wenn ich die Kontext-Klasse statisch eintippen würde brauche einen zentralen Ort, der alle möglichen Schlüssel kennt. Es könnte getan werden, aber ich würde einen flexibleren Ansatz bevorzugen. – CodingInsomnia

+0

@CodingInsomnia: Wenn Sie nicht eine begrenzte Menge von Schlüsseln haben wollen, dann ist das 'Dictionary <,>' definitiv der Weg zu gehen - aber nicht die Schlüssel als String-Literale in den Code mit ihnen hart-Code ... habe stattdessen Zeichenkettenkonstanten, um Tippfehler zu vermeiden. –

+0

Ja, absolut. Ich werde überhaupt keine Strings für Schlüssel verwenden, das war nur um den Pseudo-Code zu vereinfachen. – CodingInsomnia

2

Muss es beim ersten Anruf so schnell sein? Dank des Call-Site-Caches werden die für das dynamische Objekt erstellten Ausdrucksbäume (einschließlich der hinzugefügten Methoden) nach der Kompilierung zwischengespeichert und bei erneuter Verwendung zurückgegeben.

Die Verwendung von ExpandoObject sollte funktionieren, aber wenn Sie wirklich die absolut beste Leistung benötigen, sollten Sie vielleicht benutzerdefinierte Typen verwenden.

+0

Das klingt sehr interessant. Es ist nicht entscheidend, dass der erste Aufruf extrem schnell ist, jede Eigenschaft wird normalerweise VIELE Male aufgerufen, so dass, wenn das Abrufen eines Wertes schneller sein könnte als von einem Wörterbuch, es sich lohnen könnte. Ich denke, ich muss nur ein paar verschiedene Methoden ausprobieren. – CodingInsomnia

+0

Die beste Sache ist es, es mit vielen Dummy-Daten zu füllen und zu sehen, welche Methode am schnellsten ist. :) –

2

Wenn die Liste der Zeichenfolgen im Voraus bekannt ist, können Sie mithilfe von IL Emit einen Verzweigungsbaum basierend auf den Zeichen in der Suchzeichenfolge erstellen und zu einem Index in ein Array auflösen. Dies sollte Ihnen eine ziemlich schnelle Suchgeschwindigkeit geben.

Ich habe so etwas zum Spaß und Übung implementiert, während ich IL Emit lernte. Es funktioniert auf der Grundlage der begrenzten Testfälle, die ich ausprobiert habe, aber Sie werden es auf jeden Fall robuster machen und richtige Komponententests für den Produktionscode erstellen wollen. Ich habe den Rohcode gepostet (es ist ein bisschen lang); Sie müssten ein paar Dinge für Ihren speziellen Fall ändern, aber die Kernlogik ist da. Ich habe die EmitLdc Hilfsfunktion nicht eingeschlossen (es gibt viele Überladungen), aber es ist nur eine Funktion, um eine willkürliche Konstante auf den Stapel zu laden. Sie können die Aufrufe einfach ersetzen, um die Zeichenfolge und numerische Typen direkt mit Ldstr und Ldc_I4 jeweils zu emittieren.

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); 
    } 

EDIT: Ich habe erkannt, dass du nicht wirklich String-Schlüssel verwendet wird, so kann dies nicht zu Ihrem Fall anwendbar sein. Sie können eine ähnliche Technik mit anderen Nachschlagen verwenden, solange Sie die Möglichkeit haben, alle erforderlichen Schlüssel zusammen zu sammeln, bevor Sie sie verwenden. Ich behalte das hier für den Fall, dass es für andere nützlich sein könnte.

+0

Da er C# 4.0 verwendet, könnte er Expression Trees verwenden, um Anweisungen zu erstellen. Ich schrieb einen Beitrag darüber vor einer Weile (Aussagen ist eine der unteren): http://translate.google.com/translate?js=y&prev=_t&hl=sv&ie=UTF-8&layout=1&eotf=1&u = http% 3A% 2F% 2Fweblogs.asp.net% 2Fmikaelsoderstrom% 2Farchive% 2F2009% 2F09% 2F27% 2Ff-246-zuerst-229-expression-trees.aspx & sl = sv & tl = de Ich schrieb es in schwedisch, was ist Warum habe ich einen Link zu Google Übersetzer? :) –

+3

+1 für eine wirklich kreative Antwort - wahrscheinlich nicht in der Lage, es zu benutzen, aber immer noch ..! – CodingInsomnia