ExpandoObject и словарь с точки зрения производительности? - PullRequest
9 голосов
/ 19 августа 2010

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

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

Ответы [ 3 ]

8 голосов
/ 19 августа 2010

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

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

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

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

3 голосов
/ 20 августа 2010

Если список строк известен заранее, вы можете использовать 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);
    }

РЕДАКТИРОВАТЬ: я только что понял, что вы на самом деле не используете строковые ключи, такне применимо к вашему делу.Вы можете использовать аналогичную технику с другими поисками, если у вас есть возможность собрать все необходимые ключи вместе перед их использованием.Я оставлю это здесь на случай, если кто-то еще может найти это полезным.

2 голосов
/ 20 августа 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...