C# 从性能角度看ExpandoObject与Dictionary?
一个相当简单的问题。我正在做一个项目,需要从一种上下文存储中动态存储和检索属性值。这些值将不时写入并读取多次。检索速度是这里的首要任务,每纳秒都很重要 通常,我只是用一个字典来实现这一点,但是用C#4和ExpandoObject,我想也许有更好的方法?有人有这方面的经验吗?我在其他帖子中看到,它不是用字典实现的,这让我好奇它是快还是慢 让我试着用一些伪代码来澄清:C# 从性能角度看ExpandoObject与Dictionary?,c#,dictionary,expandoobject,C#,Dictionary,Expandoobject,一个相当简单的问题。我正在做一个项目,需要从一种上下文存储中动态存储和检索属性值。这些值将不时写入并读取多次。检索速度是这里的首要任务,每纳秒都很重要 通常,我只是用一个字典来实现这一点,但是用C#4和ExpandoObject,我想也许有更好的方法?有人有这方面的经验吗?我在其他帖子中看到,它不是用字典实现的,这让我好奇它是快还是慢 让我试着用一些伪代码来澄清: // In the main loop var context = new Context(); context["MyKey"]
// 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);
}
好吧,希望你明白我的意思
我也完全接受这里的其他建议。我一直在考虑让上下文类静态类型化的想法(例如,实际上有一个MyKey
属性,一个MyOtherKey
属性等等),虽然这可能会极大地阻碍我们的生产力
检索速度是这里的首要任务,每纳秒都很重要
与dynamic
相关的任何东西都可能不是您想要的
不要误解我的意思,它经过了大量的优化,但是如果你基本上只需要一个字符串到字符串的字典查找,请坚持使用字典
或者,如果您的键数量有限,您是否考虑过只使用一个带有枚举或一组
int
常量的数组作为键?在第一次调用时,它是否必须那么快?由于调用站点缓存,为动态对象(包括添加到动态对象中的方法)创建的表达式树在编译后被缓存,并在再次使用时返回
使用ExpandoObject应该可以,但是如果您真的需要获得绝对最佳的性能,也许您应该使用自定义类型。如果事先知道字符串列表,您可以使用IL Emit根据搜索字符串中的字符创建分支树,并将索引解析为数组。这将使您的查找速度非常快 我在学习IL Emit的时候,为了好玩和练习而实现了类似的东西。它基于我尝试过的有限测试用例工作,但您肯定希望使它更健壮,并为生产代码创建适当的单元测试。我已经发布了原始代码(有点长);你需要为你的特殊情况改变一些事情,但核心逻辑是存在的。我没有包括
EmitLdc
helper函数(有很多重载),但它只是一个将任意常量加载到堆栈的函数。您可以简单地分别使用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);
}
受保护的void GenerateNestedStringSearch(ILGenerator、T[]值、Func getName、Action loadValue)
{
//如果找不到匹配的,我们就跳到这里
Label notFound=gen.DefineLabel();
//尝试匹配字符串
GenerateNestedStringSearch(gen、notFound、value、getName、loadValue、0);
//找不到任何内容,因此不再需要字符串
gen.MarkLabel(未找到);
gen.Emit(操作码Pop);
//抛出ArgumentOutOfRangeException以指示找不到
埃米特LDC(以下简称“名称”);
gen.EmitLdc(“绑定不包含具有指定名称的标记:”);
gen.Emit(操作码Ldarg_0);
gen.Emit(opcode.Call,typeof(String).GetMethod(“Concat”,
BindingFlags.Static | BindingFlags.Public,
无效的
新[]{typeof(string),typeof(string)},
空);
gen.Emit(操作码.Newobj,
GetConstructor(新[]{typeof(string),typeof(string)});
gen.Emit(操作码抛出);
}
受保护的void GenerateNestedStringSearch(ILGenerator、Label notFound、T[]值、Func getName、Action loadValue、int charIndex)
{
//从候选字符串加载字符以进行比较
gen.Emit(操作码Dup);
埃米特尔德奇将军(查林德克斯);
gen.Emit(操作码Ldelem_U2);
//按此索引处的字符对可能的字符串进行分组
//我们忽略太短的字符串
var strings=values.Select(getName.ToArray();
var stringsByChar=
从x到字符串
其中charIndex0);
如果(计数=1)
{
//不再需要源字符或字符串
gen.Emit(操作码Pop);
gen.Emit(操作码Pop);
//返回此名称的值
int index=Array.FindIndex(strings,s=>s==grouped.strings.First());
负荷值(发电机、值[索引]);
gen.Emit(操作码Ret);
}
其他的
{
//不再需要性格了
gen.Emit(操作码Pop);
//如果存在以该字符结尾的字符串
string endString=grouped.Strings.FirstOrDefault(s=>s.Length==
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);
}