ExpandoObject vs. Dictionary с точки зрения производительности?

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

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

9
задан CodingInsomnia 19 August 2010 в 14:08
поделиться

3 ответа

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

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

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

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

8
ответ дан 4 December 2019 в 15:11
поделиться

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

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

2
ответ дан 4 December 2019 в 15:11
поделиться

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

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

3
ответ дан 4 December 2019 в 15:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: