2010-05-23 13 views
5

Po kilku ponownych testach znalazłem, że moja implementacja nie radzi sobie z bardzo dużą rekurencją. Chociaż po uruchomieniu kilku testów w Firefoksie okazało się, że może to być bardziej powszechne, niż początkowo sądziłem. Uważam, że podstawowym problemem jest to, że moja implementacja wymaga 3 wywołań, aby wykonać wywołanie funkcji. Pierwsze wywołanie dotyczy metody o nazwie Call, która zapewnia, że ​​wywoływane jest wywołanie obiektu wywoływalnego i otrzymuje wartość dowolnych argumentów, które są odwołaniami. Drugie wywołanie dotyczy metody o nazwie Call, która jest zdefiniowana w interfejsie ICallable. Ta metoda tworzy nowy kontekst wykonania i buduje wyrażenie lambda, jeśli nie zostało ono utworzone. Ostateczne wywołanie jest wykonane dla lambda, że ​​obiekt funkcji hermetyzuje. Wyraźne wywołanie funkcji jest dość ciężkie, ale jestem pewien, że przy odrobinie ulepszenia mogę uczynić rekurencję użytecznym narzędziem podczas korzystania z tej implementacji.Jak mogę poprawić możliwości rekursji mojej implementacji ECMAScript?

public static object Call(ExecutionContext context, object value, object[] args) 
{ 
    var func = Reference.GetValue(value) as ICallable; 
    if (func == null) 
    { 
     throw new TypeException(); 
    } 
    if (args != null && args.Length > 0) 
    { 
     for (int i = 0; i < args.Length; i++) 
     { 
      args[i] = Reference.GetValue(args[i]); 
     } 
    } 
    var reference = value as Reference; 
    if (reference != null) 
    { 
     if (reference.IsProperty) 
     { 
      return func.Call(reference.Value, args); 
     } 
     else 
     { 
      return func.Call(((EnviromentRecord)reference.Value).ImplicitThisValue(), args); 
     } 
    } 
    return func.Call(Undefined.Value, args); 
} 

public object Call(object thisObject, object[] arguments) 
{ 
    var lexicalEnviroment = Scope.NewDeclarativeEnviroment(); 
    var variableEnviroment = Scope.NewDeclarativeEnviroment(); 
    var thisBinding = thisObject ?? Engine.GlobalEnviroment.GlobalObject; 
    var newContext = new ExecutionContext(Engine, lexicalEnviroment, variableEnviroment, thisBinding); 
    Engine.EnterContext(newContext); 
    var result = Function.Value(newContext, arguments); 
    Engine.LeaveContext(); 
    return result; 
} 
+0

Przypuszczam, że przekształcanie rekurencji ogona w pętle jest obecnie poza zakresem? W ten sposób unikniesz całkowitego dzwonienia. –

+0

@DrJokepu - Zachowałem pomysł wykorzystania rekurencji ogona w moim umyśle, ale także szukam sugestii, jak sprawić, by rozmowy były mniej ciężkie jako ogólna poprawa wydajności. Nie wierzę też, że rekurencja ogona może być właściwie zaimplementowana w przypadkach, w których złożoność funkcji jest zbyt duża. – ChaosPandion

+0

Cóż, to nie wygląda na to, że robi coś niepotrzebnego, czy próbowałeś go uruchomić z profilerem? Chodzi mi o to, że wywołania funkcji (w trybie Release) nie są bardzo drogie w CLR (niestety drugie Call jest nieco zbyt grube, aby być podkreślonym przez JIT), więc wątpię, że dlatego jest ciężki. Może coś w Reference.GetValue() czy coś takiego? Profiler byłby zdecydowanie bardzo pomocny. –

Odpowiedz

2

Nie mogę uwierzyć, jak łatwo było pracować. Zasadniczo w moim kompilatorze sprawdzam, czy funkcja zwraca wynik samego wywołania. Jeśli tak, to zamiast tego zwracam argumenty, które są przekazywane. Następnie pobieram dowolne wartości referencyjne i ponownie wywołuję lambda. Dzięki temu mogłem wykonać miliony wywołań rekurencyjnych.

Chciałbym podziękować DrJokepu za inspirowanie tego rozwiązania.

public object Call(object thisObject, object[] arguments) 
{ 
    var lexicalEnviroment = Scope.NewDeclarativeEnviroment(); 
    var variableEnviroment = Scope.NewDeclarativeEnviroment(); 
    var thisBinding = thisObject ?? Engine.GlobalEnviroment.GlobalObject; 
    var newContext = new ExecutionContext(Engine, lexicalEnviroment, variableEnviroment, thisBinding); 
    var result = default(object); 
    var callArgs = default(object[]); 

    Engine.EnterContext(newContext); 
    while (true) 
    { 
     result = Function.Value(newContext, arguments); 
     callArgs = result as object[]; 
     if (callArgs == null) 
     { 
      break; 
     } 
     for (int i = 0; i < callArgs.Length; i++) 
     { 
      callArgs[i] = Reference.GetValue(callArgs[i]); 
     } 
     arguments = callArgs; 
    } 
    Engine.LeaveContext(); 

    return result; 
} 
Powiązane problemy