2010-05-23 4 views
5

После некоторых тестов resent я обнаружил, что моя реализация не может справиться с очень большой рекурсией. Хотя после того, как я провел несколько тестов в Firefox, я обнаружил, что это может быть более распространено, чем я думал изначально. Я считаю, что основная проблема заключается в том, что для моей реализации требуется 3 вызова для вызова функции. Первый вызов выполняется с помощью метода Call, который гарантирует, что вызов выполняется вызываемому объекту и получает значение любых аргументов, которые являются ссылками. Второй вызов производится по методу Call, который определен в интерфейсе ICallable. Этот метод создает новый контекст выполнения и строит выражение лямбда, если оно не было создано. Последний вызов выполняется для лямбда, который объект функции инкапсулирует. Очевидно, что выполнение вызова функции довольно тяжелое, но я уверен, что с небольшой настройкой я могу сделать рекурсию жизнеспособным инструментом при использовании этой реализации.Как я могу улучшить возможности рекурсии моей реализации 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

Предполагаю, что преобразование хвостовой рекурсии в циклы уже не доступно? Таким образом, вы могли бы избежать полного вызова. –

+0

@DrJokepu - Я придерживался идеи использования хвостовой рекурсии в глубине моего разума, но я также ищу предложения о том, как сделать сами звонки менее тяжелыми, как общее улучшение производительности. Также я не верю, что рекурсия хвоста может быть реализована правильно в случаях, когда сложность функции слишком велика. – ChaosPandion

+0

Ну не похоже, что это делает что-то ненужное, попробовали ли вы его запустить с профилировщиком? Я имею в виду, что вызовы функций (в режиме деблокирования) не очень дороги в CLR (к сожалению, второй вызов слишком утолщен, чтобы быть встроенным JIT), поэтому я сомневаюсь, что именно поэтому он тяжелый. Может быть, что-то в Reference.GetValue() или что-то в этом роде? Профилировщик определенно будет очень полезен. –

ответ

2

Я не могу поверить, как легко было работать. В основном в моем компиляторе я проверяю, возвращает ли функция результат вызова самого себя. Если это так, я вместо этого возвращаю аргументы, которые передаются. Затем я просто хватаю любые опорные значения и повторно вызываю бэкэнда. С этим я смог сделать миллионы рекурсивных звонков.

Я хотел бы поблагодарить DrJokepu за то, что вдохновил это решение.

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; 
} 
Смежные вопросы