代码之家  ›  专栏  ›  技术社区  ›  CodingInsomnia

从性能的角度看ExpandoObject与Dictionary?

  •  9
  • CodingInsomnia  · 技术社区  · 14 年前

    通常,我只是用一个字典来实现,但是用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..
         }
    }
    

    好吧,希望你明白我的意思。。

    我也完全愿意接受其他建议。我一直在玩弄使上下文类静态类型化的想法(即实际上有一个 MyKey 物业,a MyOtherKey

    3 回复  |  直到 14 年前
        1
  •  10
  •   Jon Skeet    14 年前

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

    dynamic 可能 不是吗

    别误会我的意思,这是相当严重的优化-但如果你 基本上 只想查一串字典,坚持查字典。

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

        2
  •  3
  •   Dan Bryant    14 年前

    如果预先知道字符串列表,则可以使用IL Emit根据搜索字符串中的字符创建分支树,并将索引解析为数组。这应该给你相当快的查找速度。

    我实现了这样的乐趣和实践,而我正在学习的东西。它是基于我尝试过的有限的测试用例工作的,但是您肯定希望它更健壮,并为生产代码创建适当的单元测试。我已经发布了原始代码(有点长);您需要为您的特定情况更改一些内容,但核心逻辑就在那里。我没有包括 EmitLdc helper函数(有很多重载),但它只是一个将任意常量加载到堆栈的函数。您可以简单地分别使用Ldstr和Ldc\u 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
  •  2
  •   Mikael Söderström    14 年前

    第一次打电话一定要那么快吗?由于调用站点缓存,为动态对象(包括添加到其中的方法)创建的表达式树在编译后被缓存,并且在您再次使用它时将被返回。

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

    推荐文章