2010-08-19 15 views
7

Una domanda piuttosto semplice. Sto lavorando a un progetto in cui ho bisogno di archiviare e recuperare i valori delle proprietà dinamicamente da un tipo di memoria di contesto. I valori verranno scritti ora e poi e letti più volte. La velocità di recupero è la massima priorità qui, e ogni nanosecondo conta.ExpandoObject vs. Dizionario dal punto di vista delle prestazioni?

Di solito, lo implementerei semplicemente con un dizionario, ma con C# 4 e ExpandoObject sto pensando che forse c'è un modo migliore? Qualcuno ha avuto qualche esperienza con questo? Ho visto in altri post che NON è implementato usando un dizionario, il che mi rende curioso di sapere se è più veloce o più lento?

Vorrei cercare di chiarire con qualche pseudo codice:

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

Beh, speriamo che si ottiene quello che sto cercando di fare ..

Sono anche completamente aperto ad altri suggerimenti qui. Ho giocato con l'idea di rendere staticamente tipizzata la classe Context (cioè avere una proprietà MyKey, una proprietà MyOtherKey, ecc.) E anche se potrebbe essere possibile, ostacolerebbe molto la produttività per noi.

+0

Sembra che tu abbia molte costanti di stringa, vuol dire che hai un elenco dei tasti del dizionario prima del tempo? Se è così, basta usare una classe di vaniglia per evitare l'overhead della tua funzione di hash. – Juliet

+0

Sì, in realtà ho usato solo le costanti di stringa per semplificare lo pseudo codice. Nell'esempio "attuale" del mondo reale sto usando chiavi più veloci. – CodingInsomnia

+0

[Ecco un semplice confronto] (http://spiritofdev.blogspot.in/2011/12/performance-of-c-40-dynamic-vs.html). Se è possibile ignorare il costo di costruzione, le ricerche dovrebbero essere eseguite in modo simile. – nawfal

risposta

6

La velocità di recupero è la massima priorità qui, e ogni nanosecondo conta.

a che fare con dynamic probabilmente non è quello che stai cercando per poi ...

Non fraintendetemi, è piuttosto pesantemente ottimizzato - ma se si fondamentalmente solo vuoi una ricerca da dizionario stringa-a-stringa, bastone con un dizionario.

In alternativa, se si dispone di un numero limitato di chiavi, si è considerato solo un array con enum o un gruppo di costanti int come chiavi?

+0

Grazie, è stato un po 'quello che mi aspettavo (ma non quello che speravo ..) Penso che il problema con un array semplice sarebbe più o meno lo stesso come se andassi a digitare staticamente la classe Context, cioè io bisogno di una posizione centrale che conosca tutte le possibili chiavi. Potrebbe essere fatto, ma preferirei avere un approccio più flessibile. – CodingInsomnia

+0

@CodingInsomnia: se non si desidera avere un set limitato di chiavi, il 'Dictionary <,>' è sicuramente la strada da percorrere - ma non codificare i tasti come valori letterali stringa nel codice che li usa ... avere costanti stringa, per evitare errori di battitura. –

+0

Sì, assolutamente. In realtà non userò mai le stringhe per le chiavi, solo per semplificare il codice psuedo. – CodingInsomnia

2

Dev'essere così veloce alla prima chiamata? Grazie alla cache del sito di chiamata, gli alberi di espressioni creati per l'oggetto dinamico (compresi i metodi che gli vengono aggiunti) vengono memorizzati nella cache dopo la sua compilazione e verranno restituiti quando lo utilizzi nuovamente.

L'utilizzo di ExpandoObject dovrebbe funzionare, ma se è davvero necessario ottenere prestazioni assolutamente migliori, forse è necessario utilizzare i tipi personalizzati.

+0

Sembra molto interessante. Non è cruciale che la prima chiamata sia estremamente veloce, ogni proprietà verrà tipicamente chiamata MOLTO volte, quindi se recuperare un valore potrebbe essere più veloce di un dizionario, potrebbe valerne la pena. Credo che dovrò provare alcuni metodi diversi. – CodingInsomnia

+0

La cosa migliore da fare è solo popolarlo con molti dati fittizi e vedere quale metodo è il più veloce. :) –

2

Se l'elenco di stringhe è noto in anticipo, è possibile utilizzare IL Emit per creare un albero di diramazione basato sui caratteri nella stringa di ricerca e risolvere un indice in un array. Questo dovrebbe darti una velocità di ricerca piuttosto veloce.

Ho implementato qualcosa di simile per divertimento e pratica mentre stavo imparando IL Emit. Funziona in base ai casi di test limitati che ho provato, ma vorrete sicuramente renderlo più robusto e creare test unitari adeguati per il codice di produzione. Ho pubblicato il codice raw (è un po 'lungo); avresti bisogno di cambiare alcune cose per il tuo caso particolare, ma la logica di base è lì. Non ho incluso la funzione helper EmitLdc (ci sono molti sovraccarichi), ma è solo una funzione per caricare una costante arbitraria nello stack. È possibile semplicemente sostituire le chiamate per emettere la stringa e i tipi numerici direttamente utilizzando rispettivamente Ldstr e 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); 
    } 

EDIT: Ho appena realizzato che non si è in realtà utilizza chiavi stringa, quindi questo potrebbe non essere applicabile al vostro caso. È possibile utilizzare una tecnica simile con altre ricerche purché si abbia la possibilità di raccogliere tutte le chiavi richieste insieme prima di usarle. Terrò questo qui nel caso in cui qualcun altro potrebbe trovare utile.

+0

Dato che sta usando C# 4.0, potrebbe usare Expression Trees per creare istruzioni. Ho scritto un post su di esso per un po 'di tempo fa (le dichiarazioni sono in fondo): 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-rst-229-expression-trees.aspx & sl = sv & tl = it L'ho scritto in svedese, che è perché ho un link a Google Translate. :) –

+3

+1 per una risposta davvero creativa - probabilmente non sarà in grado di usarlo, ma comunque ..! – CodingInsomnia

Problemi correlati