2010-08-19 30 views
7

真的很簡單的問題。我正在開發一個項目,我需要從一種上下文存儲中動態地存儲和檢索屬性值。這些值將被立即寫入並讀取多次。檢索速度是當務之急,每納秒都是重要的。ExpandoObject與字典從性能的角度?

通常,我只是簡單地用一個字典來實現它,但是用C#4和ExpandoObject我想這可能有更好的方法嗎?有沒有人有任何經驗呢?我在其他文章中看到過,它不是使用Dictionary來實現的,這讓我好奇它是更快還是更慢?

讓我嘗試了一些僞代碼,以澄清:

// 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屬性等等)的想法,儘管這可能會對我們產生很大的影響。

+0

它看起來像你有很多字符串常量,這是否意味着你有一個你的字典鍵提前列表?如果是這樣,只需使用一個vanilla類來避免散列函數的開銷。 – Juliet 2010-08-19 14:17:49

+0

是的,我實際上只使用字符串常量來簡化僞代碼。在「當前」現實世界的例子中,我使用的是更快的鍵。 – CodingInsomnia 2010-08-19 14:25:02

+0

[這是一個簡單的比較](http://spiritofdev.blogspot.in/2011/12/performance-of-c-40-dynamic-vs.html)。如果您可以忽略構建成本,則查找應該以相似的方式執行。 – nawfal 2014-07-17 22:49:01

回答

6

檢索速度是當務之急,每納秒計數。

任何與dynamic可能不是你在找什麼,然後......

不要誤會我的意思,這是非常嚴重的優化 - 但是如果你基本上剛想要一個字符串到字符串的字典查找,堅持一個字典。

或者,如果你的鍵數量有限,你有沒有考慮過只有一個枚舉或者一串int常量作爲鍵?

+0

謝謝,那是我所期望的(但不是我所希望的)..我認爲這個簡單數組的問題與我靜態鍵入Context類相同,即I需要知道所有可能的密鑰的中心位置。它可以完成,但我寧願有一個更靈活的方法。 – CodingInsomnia 2010-08-19 14:30:50

+0

@CodingInsomnia:如果你不想擁有一組有限的密鑰,那麼'Dictionary <,>'絕對是一種可行的方式 - 但不要將密鑰硬編碼爲代碼中的字符串文字......有相反的字符串常量,以避免錯別字。 – 2010-08-19 14:42:03

+0

是的,絕對。我實際上不會爲鍵使用字符串,這只是爲了簡化僞代碼。 – CodingInsomnia 2010-08-19 20:20:12

2

在第一次打電話時它是否必須快?感謝網站緩存,爲動態對象(包括您添加的方法)創建的表達式樹在編譯後會被緩存,並在您再次使用時返回。

使用ExpandoObject應該可以工作,但如果您確實需要獲得絕對最佳的性能,那麼可能應該使用自定義類型。

+0

這聽起來很有趣。第一次調用非常迅速並不重要,每個屬性通常會被稱爲很多次,所以如果檢索一個值比字典更快,它可能是值得的。我想我只需要嘗試幾種不同的方法。 – CodingInsomnia 2010-08-19 20:18:49

+0

最好的做法是填充大量的虛擬數據,並查看哪種方法最快。 :) – 2010-08-19 20:29:54

2

如果預先知道字符串列表,則可以使用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); 
    } 

編輯:我只是意識到,你實際上並沒有使用字符串鍵,所以這可能並不適用於你的情況。只要您能夠在使用它們之前收集所有必需的密鑰,就可以使用類似的技術與其他查找功能。我會保留這裏,以防其他人可能會覺得它有用。

+0

由於他使用C#4.0,他可以使用Expression Trees來構建語句。 我前一段時間寫了一篇關於它的文章(聲明是其底部): 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 = en 我在瑞典語中寫道爲什麼我有一個鏈接到谷歌翻譯。 :) – 2010-08-19 20:41:12

+3

+1一個真正有創意的答案 - 可能無法使用它,但仍然..! – CodingInsomnia 2010-08-20 07:18:55