10

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

通常,我会简单地使用字典来实现它,但是使用 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属性等),虽然它可能会极大地阻碍我们的生产力。

4

3 回答 3

10

检索速度是这里的重中之重,每一纳秒都很重要。

与此有关的任何事情dynamic可能都不是您要寻找的...

不要误会我的意思,它已经过大量优化 - 但如果您基本上只是想要一个字符串到字符串的字典查找,请坚持使用字典。

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

于 2010-08-19T14:20:26.357 回答
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);
    }

编辑:我刚刚意识到您实际上并没有使用字符串键,因此这可能不适用于您的情况。只要您能够在使用它们之前将所有必需的键收集在一起,就可以将类似的技术用于其他查找。我会把它留在这里,以防其他人发现它有用。

于 2010-08-19T20:33:32.767 回答
2

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

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

于 2010-08-19T20:02:15.167 回答