一个相当简单的问题。我正在开发一个项目,我需要从一种上下文存储中动态存储和检索属性值。这些值将立即写入并多次读取。检索速度是这里的首要任务,每纳秒都很重要。
通常,我只是用一个字典来实现它,但是使用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
属性等),尽管它可能会对生产力产生很大的影响。我们。
答案 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应该可以工作,但如果你真的需要获得绝对最佳的性能,也许你应该使用自定义类型。