【发布时间】:2011-07-19 17:45:12
【问题描述】:
在 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,这在我的笔记本电脑 (win7 x64) 上运行时间为 0.270 秒。用两个 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 秒(Func<object, object, object> 需要 5.1 秒)- 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]; } }
}
可以从 IDynamicMetaObjectProvider 创建 ScriptScope,我希望它由 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 只会为未定义的属性调用。
可能为了获得合理的性能,我需要为我的 RowIterator 实现 IDynamicMetaObjectProvider 以使用动态 CallSites,但找不到适合我的示例。在我的实验中,我不知道如何在 BindGetMember 中处理 __builtins__:
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。如有任何关于如何改进设计和/或性能的建议,我将不胜感激。
【问题讨论】:
-
我不会将 IDMOP 用作 ScriptScope 的成员,而是将 RowIterator 注入到 ScriptScope 中,或者甚至作为您要离开范围的委托的参数。
-
我没有将 IDMOP 用作 ScriptScope 的 成员,而是用作“上下文”本身,即在我的表达式中我想输入“a / b”而不是“row.a / row.b”。这怎么能用你所谓的注入来完成?
-
@Dino:我认为使用 RowIterator 作为委托的参数是一个关于性能的好主意。如何将用户表达式“a / b”(或其他)替换为“row.a / row.b”,而我将实际编译它?
标签: performance c#-4.0 ironpython expression-trees dynamic-language-runtime