2010-08-19 6 views
7

Довольно простой вопрос. Я работаю над проектом, где мне нужно динамически сохранять и извлекать значения свойств из своего хранилища контекста. Значения будут записываться время от времени и читать несколько раз. Скорость поиска - главный приоритет здесь, и каждая наносекунда считается.ExpandoObject vs. Словарь с точки зрения производительности?

Обычно я просто реализую это с помощью Словаря, но с 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 и т. Д.), И, хотя это может быть возможно, это значительно затруднит нам производительность.

+0

Похоже, у вас много строковых констант, значит ли это, что у вас есть список ваших словарных ключей раньше времени? Если это так, просто используйте класс ванили, чтобы избежать накладных расходов на хеш-функцию. – Juliet

+0

Да, я фактически использовал только строковые константы, чтобы упростить псевдокод. В «текущем» реальном примере я использую быстрые клавиши. – CodingInsomnia

+0

[Вот простое сравнение] (http://spiritofdev.blogspot.in/2011/12/performance-of-c-40-dynamic-vs.html). Если вы можете игнорировать стоимость строительства, поиск должен выполняться аналогичным образом. – nawfal

ответ

6

Скорость поиска - главный приоритет здесь, и каждый наносекунда считается.

Все, что делать с dynamic вероятно не то, что вы ищете, то ...

Не поймите меня неправильно, это довольно сильно оптимизированы - но если вы в основном просто хотите, чтобы поиск по строкам в строку, вставьте словарь.

В качестве альтернативы, если у вас есть ограниченное количество ключей, считаете ли вы, что у вас есть массив с переименованием или связкой из int констант в качестве ключей?

+0

Спасибо, это было то, чего я ожидал (но не то, на что я надеялся ..) Я думаю, что проблема с простым массивом будет почти такой же, как если бы я пошел со статической типизацией класса Context, т.е. я необходимо центральное место, которое знает обо всех возможных ключах. Это можно сделать, но я предпочел бы иметь более гибкий подход. – CodingInsomnia

+0

@CodingInsomnia: Если вы не хотите иметь ограниченный набор ключей, то «Словарь <,>» определенно подходит для того, чтобы идти - но не перекодируйте ключи как строковые литералы в код, используя их ... вместо этого есть строковые константы, чтобы избежать опечаток. –

+0

Да, абсолютно. Я вообще не собираюсь использовать строки для ключей, это просто для упрощения кода psuedo. – CodingInsomnia

2

Должно ли быть так быстро при первом звонке? Благодаря кешу сайта вызова деревья выражений, созданные для динамического объекта (включая методы, которые вы добавляете к нему), кэшируются после его компиляции и будут возвращены при повторном использовании.

Использование ExpandoObject должно работать, но если вам действительно нужно получить абсолютно лучшую производительность, возможно, вы должны использовать нестандартные типы.

+0

Звучит очень интересно. Не обязательно, чтобы первый вызов был чрезвычайно быстрым, каждое свойство обычно называлось LOT раз, поэтому, если получение значения может быть быстрее, чем из словаря, это может стоить того. Думаю, мне просто нужно попробовать несколько разных методов. – CodingInsomnia

+0

Лучше всего просто заполнить его множеством фиктивных данных и посмотреть, какой из них самый быстрый. :) –

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

EDIT: Я просто понял, что вы на самом деле не используя строковые ключи, так что это не может быть применимо к данному случаю. Вы можете использовать подобную технику с другими поисковыми системами, если у вас есть возможность собрать все необходимые ключи вместе перед их использованием. Я сохраню это здесь, если кто-то другой найдет это полезным.

+0

Поскольку он использует C# 4.0, он может использовать деревья выражений для создания операторов. Я написал сообщение об этом на некоторое время назад (заявления - это его нижняя часть): 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 Я написал его на шведском языке, который почему у меня есть ссылка на Google Translate. :) –

+3

+1 для действительно творческого ответа - вероятно, не сможет его использовать, но все же ..! – CodingInsomnia

Смежные вопросы