从性能的角度来看ExpandoObject与Dictionary的关系?

时间:2010-08-19 14:08:24

标签: c# dictionary expandoobject

一个相当简单的问题。我正在开发一个项目,我需要从一种上下文存储中动态存储和检索属性值。这些值将立即写入并多次读取。检索速度是这里的首要任务,每纳秒都很重要。

通常,我只是用一个字典来实现它,但是使用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 个答案:

答案 0 :(得分:8)

  

检索速度是这里的首要任务,每纳秒都很重要。

dynamic可能的任何关系都不是你正在寻找的......

不要误解我的意思,它已经过了很大程度的优化 - 但是如果你基本上只想要一个字符串到字符串的字典查找,那就坚持使用字典。

或者,如果您的密钥数量有限,您是否考虑过只使用枚举或一堆int常量作为密钥的数组?

答案 1 :(得分:3)

如果事先知道字符串列表,则可以使用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 :(得分:2)

第一次通话时是否必须快?由于调用站点缓存,为动态对象创建的表达式树(包括添加到它的方法)在编译后会被缓存,并在您再次使用时返回。

使用ExpandoObject应该可以工作,但如果你真的需要获得绝对最佳的性能,也许你应该使用自定义类型。