2010-09-10 7 views
3

У меня есть очень рекурсивная функция, которая должна теоретически работать хорошо даже с большими входами. Проблема во время написания я забыл, что C# не очень хорошо оптимизирует оптимизацию хвоста, если вообще, так что я получаю StackOverflowException s для любого сложного ввода. Основная структура метода состоит из двух больших методов, каждый из которых вызывает другой.Оптимизация хвостовых вызовов в C#

public object Simplify(object param) { 
    if (IsSimple(param)) 
     return param; 
    else 
     return Expand(param); 
} 

private object Expand(object param) { 
    return Simplify(ProcessExpansion(param)); 
} 

Оба IsSimple и ProcessExpansion имеют относительно неподвижную глубину стека - единственная проблема состоит в рекурсии между Simplify и Expand. Как вы собираетесь уменьшить глубину стека здесь?

Я думал о возвращении делегатов, которые рассчитывали бы результат, но это кажется излишним - должен быть более простой способ. Это моя мысль о решении (это не очень полированный, потому что я продолжаю думать, что я должен делать что-то ужасно неправильно здесь):

public object Simplify(object param) { 
    object result =() => SimplifyInternal(param); 
    while (result is Func<object>) 
     result = ((Func<object>)result)(); 
    return result; 
} 

private object SimplifyInternal(object param) { 
    if (IsSimple(param)) 
     return param; 
    else 
     return Expand(param); 
} 

private object Expand(object param) { 
    return() => SimplifyInternal(ProcessExpansion(param)); 
} 

Так что мои два вопроса:

  1. Что это что ужасно плохо с этим решением?
  2. Можете ли вы подумать о лучшем?
+1

Просто отметим, что среда выполнения .NET 4.0 x64 оптимизирует хвостовые вызовы, а x86 - нет. Совершенно глупо, я знаю. –

+1

Почему бы не прог это в F #? Я бы предпочел, чтобы компилятор оптимизировал его (функция языка), а затем помолитесь, что дрожание это выяснит. – MrDosu

+2

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

ответ

8

Разве это не просто

while(!IsSimple(param)) 
    param = ProcessExpansion(param); 
return param; 

?

+0

Точка, если A называет B только в положении хвоста, а B называет A только в положении хвоста, тогда вы должны сможете немного массировать, чтобы объединить два метода и обернуть вокруг него цикл while и получить ту же семантику. – Brian

+0

Это так. Однако пример значительно упрощается: функции достаточно сложны, как есть, и каждый делает что-то совершенно другое. Существует также некоторый полиморфизм, связанный с различными типами значений, которые могут иметь параметры. Я мог бы объединить их в одну функцию теоретически, но сделать их намного сложнее для поддержания. – configurator

+0

Действительно. В этом случае одной из возможных возможностей является запись этих функций в F #. Другой - использовать батут (похоже, вы пытались это сделать). Еще один способ - использовать потоки (например, когда стек становится высоким, QueueUserWorkItem и блокирует результат).Ни один из этих вариантов не является чрезвычайно привлекательным; используйте тот, который вам удобен при создании и обслуживании. Кроме того, подумайте, можете ли вы «изменить проблему», чтобы проблема исчезла в первую очередь. – Brian