Довольно простой вопрос. Я работаю над проектом, в котором мне нужно динамически хранить и извлекать значения свойств из своего рода контекстного хранилища. Значения будут записываться время от времени и читаться несколько раз. Скорость поиска является главным приоритетом, и каждая наносекунда считается.
Обычно я просто реализую это с помощью словаря, но с 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
и т. Д.), И хотя это могло бы быть возможным, это могло бы снизить производительность довольно много для нас.
Скорость извлечения является здесь главным приоритетом, и каждая наносекунда на счету.
Все, что связано с динамическим
, вероятно, не то, что вы ищете ...
Не поймите меня неправильно, он довольно сильно оптимизирован - но если вы в основном просто хотите поиск по словарю из строки в строку, используйте словарь.
В качестве альтернативы, если у вас ограниченное количество ключей, рассматривали ли вы просто массив с перечислением или набором констант int
в качестве ключей?
Должен ли он быть таким быстрым при первом звонке? Благодаря кешу сайта вызова деревья выражений, созданные для динамического объекта (включая методы, которые вы добавляете к нему), кэшируются после его компиляции и будут возвращены при повторном использовании.
Использование ExpandoObject должно работать, но если вам действительно нужно добиться максимальной производительности, возможно, вам следует использовать настраиваемые типы.
Если список строк известен заранее, вы можете использовать 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);
}
РЕДАКТИРОВАТЬ: Я только что понял, что на самом деле вы не используете строковые ключи, поэтому это может быть неприменимо к вашему случаю. Вы можете использовать аналогичную технику с другими поисковыми запросами, если у вас есть возможность собрать все необходимые ключи перед их использованием. Я сохраню это здесь на случай, если кому-нибудь это пригодится.