ExpandoObject vs. Dictionary in terms of performance?

Pretty simple question. I am working on a project where I need to dynamically save and retrieve property values ​​from my context store. Values ​​will be written from time to time and read several times. Search speed is the top priority here, and every nanosecond counts.

Usually I just implement this with a Dictionary, but with C # 4 and ExpandoObject. I think that maybe there is a better way? Does anyone have any experience? I have seen in other posts that it is NOT implemented using the Dictionary, which makes me think about whether it’s faster or slower?

Let me clarify with some pseudo code:

// 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 only Pseudo-code.. } } 

Ok, hope you get what I'm trying to do.

I am also fully open to other offers here. I played with the idea of ​​making a static type of the Context class (i.e., actually having the MyKey property, the MyOtherKey property, etc.)), and although this may be possible, this will make performance very difficult for us.

+7
dictionary c # expandoobject
source share
3 answers

Search speed is the top priority here, and every nanosecond counts.

Everything related to dynamic is probably not what you are looking for then ...

Don’t get me wrong, this is pretty optimized, but if you basically just want to find the dictionary on a line-by-line basis, stick with the dictionary.

Alternatively, if you have a limited number of keys, do you think you have an array with an enumeration or a bunch of int constants as keys?

+6
source share

Should it be so fast on the first call? Thanks to the call node cache, the expression trees created for the dynamic object (including the methods that you add to it) are cached after it is compiled and will be returned when reused.

Using ExpandoObject should work, but if you really need to get the absolutely best performance, maybe you should use non-standard types.

+2
source share

If the list of strings is known in advance, you can use IL Emit to create a branching tree based on the characters in the search string and permission to index into the array. This should give you a pretty fast search speed.

I implemented something similar for fun and practice when I studied IL Emit. It works based on the limited test cases I've tried, but you definitely want to make it more reliable and create the right unit tests for production code. I published the source code (it is long); You will need to change a few things for your specific case, but there is a basic logic. I did not include the EmitLdc helper function (there are many overloads), but it is just a function for loading an arbitrary constant EmitLdc stack. You can simply replace calls to emit string and number types directly using Ldstr and Ldc_I4, respectively.

  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); } 

EDIT: I just realized that you are not really using string keys, so this may not apply to your case. You can use a similar technique with other search engines if you have the opportunity to collect all the necessary keys together before using them. I will save it here if someone else finds it useful.

+2
source share

All Articles