В приложении C # -4.0 у меня есть Словарь строго типизированных IList, имеющих одинаковую длину - динамически строго типизированная таблица на основе столбцов.
Я хочу, чтобы пользователь предоставил одно или несколько (python-) выражений на основе доступных столбцов, которые будут агрегированы по всем строкам. В статическом контексте это будет:
IDictionary<string, IList> table;
// ...
IList<int> a = table["a"] as IList<int>;
IList<int> b = table["b"] as IList<int>;
double sum = 0;
for (int i = 0; i < n; i++)
sum += (double)a[i] / b[i]; // Expression to sum up
При n = 10 ^ 7 это работает на моем ноутбуке за 0,270 с (win7 x64). Замена выражения делегатом с двумя аргументами типа int занимает 0,580 с, для нетипизированного делегата - 1,19 с.
Создание делегата из IronPython с
IDictionary<string, IList> table;
// ...
var options = new Dictionary<string, object>();
options["DivisionOptions"] = PythonDivisionOptions.New;
var engine = Python.CreateEngine(options);
string expr = "a / b";
Func<int, int, double> f = engine.Execute("lambda a, b : " + expr);
IList<int> a = table["a"] as IList<int>;
IList<int> b = table["b"] as IList<int>;
double sum = 0;
for (int i = 0; i < n; i++)
sum += f(a[i], b[i]);
это занимает 3,2 с (и 5,1 с при Func<object, object, object>
) - коэффициент от 4 до 5,5. Это ожидаемые накладные расходы на то, что я делаю? Что можно улучшить?
Если у меня много столбцов, выбранный выше подход больше не будет достаточным. Одним из решений может быть определение обязательных столбцов для каждого выражения и использование только таких в качестве аргументов. Другое решение, которое я безуспешно пытался использовать ScriptScope и динамически разрешать столбцы. Для этого я определил RowIterator, у которого есть RowIndex для активной строки и свойство для каждого столбца.
class RowIterator
{
IList<int> la;
IList<int> lb;
public RowIterator(IList<int> a, IList<int> b)
{
this.la = a;
this.lb = b;
}
public int RowIndex { get; set; }
public int a { get { return la[RowIndex]; } }
public int b { get { return lb[RowIndex]; } }
}
ScriptScope может быть создан из IDynamicMetaObjectProvider, который, как я ожидал, будет реализован динамическим C #, но во время выполнения engine.CreateScope (IDictionary) пытается вызвать, что не удается.
dynamic iterator = new RowIterator(a, b) as dynamic;
var scope = engine.CreateScope(iterator);
var expr = engine.CreateScriptSourceFromString("a / b").Compile();
double sum = 0;
for (int i = 0; i < n; i++)
{
iterator.Index = i;
sum += expr.Execute<double>(scope);
}
Далее я попытался позволить RowIterator наследоваться от DynamicObject и сделал его в работающем примере - с ужасной производительностью: 158 сек.
class DynamicRowIterator : DynamicObject
{
Dictionary<string, object> members = new Dictionary<string, object>();
IList<int> la;
IList<int> lb;
public DynamicRowIterator(IList<int> a, IList<int> b)
{
this.la = a;
this.lb = b;
}
public int RowIndex { get; set; }
public int a { get { return la[RowIndex]; } }
public int b { get { return lb[RowIndex]; } }
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (binder.Name == "a") // Why does this happen?
{
result = this.a;
return true;
}
if (binder.Name == "b")
{
result = this.b;
return true;
}
if (base.TryGetMember(binder, out result))
return true;
if (members.TryGetValue(binder.Name, out result))
return true;
return false;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (base.TrySetMember(binder, value))
return true;
members[binder.Name] = value;
return true;
}
}
Я был удивлен, что TryGetMember вызывается с именем свойств. Из документации я бы ожидал, что TryGetMember будет вызываться только для неопределенных свойств.
Вероятно, для ощутимой производительности мне нужно было бы реализовать IDynamicMetaObjectProvider для моего RowIterator, чтобы использовать динамические CallSites, но я не смог найти подходящий пример для начала. В своих экспериментах я не знал, как обрабатывать __builtins__
в BindGetMember:
class Iterator : IDynamicMetaObjectProvider
{
IList<int> la;
IList<int> lb;
public Iterator(IList<int> a, IList<int> b)
{
this.la = a;
this.lb = b;
}
public int RowIndex { get; set; }
public int a { get { return la[RowIndex]; } }
public int b { get { return lb[RowIndex]; } }
public DynamicMetaObject GetMetaObject(Expression parameter)
{
return new MetaObject(parameter, this);
}
private class MetaObject : DynamicMetaObject
{
internal MetaObject(Expression parameter, Iterator self)
: base(parameter, BindingRestrictions.Empty, self) { }
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
switch (binder.Name)
{
case "a":
case "b":
Type type = typeof(Iterator);
string methodName = binder.Name;
Expression[] parameters = new Expression[]
{
Expression.Constant(binder.Name)
};
return new DynamicMetaObject(
Expression.Call(
Expression.Convert(Expression, LimitType),
type.GetMethod(methodName),
parameters),
BindingRestrictions.GetTypeRestriction(Expression, LimitType));
default:
return base.BindGetMember(binder);
}
}
}
}
Я уверен, что мой код выше неоптимальный, по крайней мере он еще не обрабатывает IDictionary столбцов. Буду благодарен за любые советы по улучшению дизайна и / или производительности.