4

在C#-4.0應用程序中,我有一個長度相同的強類型IList字典 - 一個動態強類型的基於列的表。 我希望用戶提供一個或多個(python-)表達式,這些表達式基於將在所有行上聚合的可用列。在靜態上下文將是:IronPython中批量評估表達式的性能

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秒。 與

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]); 

創建從IronPython的委託它需要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只會被調用爲未定義的屬性。

可能爲了一個合理的性能,我需要爲我的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的呢。對於如何改進設計和/或性能的建議,我將不勝感激。

+0

而不是使用IDMOP作爲ScriptScope的成員,我會將RowIterator注入到ScriptScope中,或者甚至作爲參數傳遞給範圍外的委託。 – 2011-03-22 01:32:11

+0

我沒有使用IDMOP作爲ScriptScope的_member_,而是作爲「上下文」本身,即在我的表達式中,我想輸入「a/b」而不是「row.a/row.b」。這怎麼可以用你所謂的注入來完成呢? – Christian 2011-03-22 10:50:03

+0

@迪諾:我認爲使用RowIterator作爲委託的參數是一個關於性能的好主意。我如何將用戶表達式「a/b」(或其他)替換爲「row.a/row.b」,我將實際編譯它? – Christian 2011-04-04 16:40:53

回答

0

雖然我不知道你的情況中的所有具體細節,但是在IronPython中做這個低級的事情只有5x的放緩實際上是相當不錯的。 Computer Languages Benchmark Game中的大多數條目顯示10-30倍放緩。

其中一個主要原因是IronPython必須考慮到你在運行時做了一些鬼鬼祟祟的事情,因此無法產生相同效率的代碼。

+0

只是爲了澄清:「動態強類型」是指我的表實現。通過輸入ILists字典,我可以在運行時添加_typed_列。我認爲這應該有助於創建CallSites。 – Christian 2011-03-21 16:54:45

+0

@Christian - 啊,好的。據此編輯答案。 – 2011-03-21 17:46:40

1

我還比較了IronPython與C#實現的性能。該表達式很簡單,只需在指定的索引處添加兩個數組的值即可。直接訪問陣列提供了基線和理論最優化。通過符號字典訪問值仍然具有可接受的性能。

第三個測試創建了一個來自天真(而不是意圖)表達式樹的委託,沒有任何花哨的東西,比如調用端緩存,但它比IronPython更快。

通過IronPython腳本化表達式需要大部分時間。我的分析器顯示我大部分時間都花在PythonOps.GetVariable,PythonDictionary.TryGetValue和PythonOps.TryGetBoundAttr上。我認爲還有改進的空間。

時序:

  • 直接:00:00:00.0052680
  • 經由字典:00:00:00.5577922
  • 編譯代表:00:00:03.2733377
  • 腳本:00:00: 09.0485515

下面的代碼:

public static void PythonBenchmark() 
    { 
     var engine = Python.CreateEngine(); 

     int iterations = 1000; 
     int count = 10000; 

     int[] a = Enumerable.Range(0, count).ToArray(); 
     int[] b = Enumerable.Range(0, count).ToArray(); 

     Dictionary<string, object> symbols = new Dictionary<string, object> { { "a", a }, { "b", b } }; 

     Func<int, object> calculate = engine.Execute("lambda i: a[i] + b[i]", engine.CreateScope(symbols)); 

     var sw = Stopwatch.StartNew(); 

     int sum = 0; 

     for (int iteration = 0; iteration < iterations; iteration++) 
     { 
      for (int i = 0; i < count; i++) 
      { 
       sum += a[i] + b[i]; 
      } 
     } 

     Console.WriteLine("Direct: " + sw.Elapsed); 



     sw.Restart(); 
     for (int iteration = 0; iteration < iterations; iteration++) 
     { 
      for (int i = 0; i < count; i++) 
      { 
       sum += ((int[])symbols["a"])[i] + ((int[])symbols["b"])[i]; 
      } 
     } 

     Console.WriteLine("via Dictionary: " + sw.Elapsed); 



     var indexExpression = Expression.Parameter(typeof(int), "index"); 
     var indexerMethod = typeof(IList<int>).GetMethod("get_Item"); 
     var lookupMethod = typeof(IDictionary<string, object>).GetMethod("get_Item"); 
     Func<string, Expression> getSymbolExpression = symbol => Expression.Call(Expression.Constant(symbols), lookupMethod, Expression.Constant(symbol)); 
     var addExpression = Expression.Add(
           Expression.Call(Expression.Convert(getSymbolExpression("a"), typeof(IList<int>)), indexerMethod, indexExpression), 
           Expression.Call(Expression.Convert(getSymbolExpression("b"), typeof(IList<int>)), indexerMethod, indexExpression)); 
     var compiledFunc = Expression.Lambda<Func<int, object>>(Expression.Convert(addExpression, typeof(object)), indexExpression).Compile(); 

     sw.Restart(); 
     for (int iteration = 0; iteration < iterations; iteration++) 
     { 
      for (int i = 0; i < count; i++) 
      { 
       sum += (int)compiledFunc(i); 
      } 
     } 

     Console.WriteLine("Compiled Delegate: " + sw.Elapsed); 



     sw.Restart(); 
     for (int iteration = 0; iteration < iterations; iteration++) 
     { 
      for (int i = 0; i < count; i++) 
      { 
       sum += (int)calculate(i); 
      } 
     } 

     Console.WriteLine("Scripted: " + sw.Elapsed); 
     Console.WriteLine(sum); // make sure cannot be optimized away 
    }