4

C# -4.0 응용 프로그램에서 동적으로 강력한 형식의 열 기반 테이블과 동일한 길이를 가진 강력한 형식의 ILists 사전이 있습니다. 사용자가 모든 행에 대해 집계 할 수있는 사용 가능한 열을 기반으로 하나 이상의 (파이썬 -) 표현식을 제공하기를 원합니다. 정적 컨텍스트에서는 다음과 같습니다.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의 경우이 값은 랩톱 (win7 x64)에서 0.270 초입니다. 두 개의 int 인자를 가진 델리게이트로 표현식을 대체하기 위해서는 타입이없는 델리게이트 1.19 초 동안 0.580 초가 걸립니다. 5.5 요인 4 -

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 초 (및 Func<object, object, object> 5.1 초) 소요됩니다. 이것은 내가하고있는 일에 대해 예상되는 오버 헤드입니까? 무엇을 개선 할 수 있습니까?

많은 열이있는 경우 위에서 선택한 방법으로는 충분하지 않습니다. 한 가지 해결책은 각 표현식에 필요한 열을 결정하고 인수로만 사용하는 것입니다. 필자가 시도하지 못한 다른 해결책은 ScriptScope를 사용하고 동적으로 열을 해결하는 것이 었습니다. 이를 위해 활성 행에 대한 RowIndex와 각 열에 대한 특성을 가진 RowIterator를 정의했습니다.

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가 나는 C#의 동적에 의해 구현 될 것으로 예상 IDynamicMetaObjectProvider, 만들 수 있습니다 -하지만에서 런타임 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는 정의되지 않은 속성에 대해서만 호출 될 것이라고 예상했습니다.

아마도 합리적인 성능을 위해 동적 CallSites를 사용하기 위해 RowIterator에 IDynamicMetaObjectProvider를 구현해야하지만, 적합한 예제를 찾을 수 없었습니다. 내 실험에서 나는 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

ScriptScope의 멤버로 IDMOP을 사용하는 대신 RowIterator를 ScriptScope에 삽입하거나 범위에서 벗어나는 델리게이트의 매개 변수로 삽입합니다. –

+0

저는 ScriptScope의 _member_으로 IDMOP을 사용하지 않고 "컨텍스트"자체, 즉 "row.a/row.b"대신 "a/b"를 입력하고 싶습니다. 당신이 주사라고 부르는 것에 어떻게 이것을 할 수 있습니까? – Christian

+0

@Dino : 대리자의 인수로 RowIterator를 사용하는 것이 성능과 관련하여 좋은 아이디어라고 생각합니다. 어떻게하면 "a/b"(또는 무엇이든)를 "row.a/row.b"로 대체 할 수 있습니다. 대신 실제로 컴파일 할 것입니다. – Christian

답변

0

귀하의 경우에 세부적인 사항을 모두 알지는 못하지만, IronPython에서 이러한 낮은 수준의 작업을 수행하는 데 5 배의 속도 저하가 실제로 적용됩니다. Computer Languages Benchmark Game에있는 대부분의 항목은 10-30x 둔화를 보여줍니다.

IronPython은 런타임시 부적절한 작업을 수행 할 가능성을 허용해야하므로 동일한 효율성의 코드를 생성 할 수 없습니다.

+0

명확히하기 : "동적으로 강하게 입력 된"은 내 테이블 구현을 나타냅니다. 입력 된 ILists 사전을 사용하여 런타임에 _typed_ 열을 추가 할 수 있습니다. CallSites를 만들 때 이것이 도움이된다고 생각했습니다. – Christian

+0

@Christian - 아, 알았어. 그에 따라 수정 된 답변. –

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 코드입니다 10