LINUX.ORG.RU

Java - неудачная попытка перенести вычисления со стека в кучу

 ,


0

1
public class Main {

    private static int sumRec(int n) {return n>0 ? n + sumRec(n-1) : 0;}

    private static class Eval {
        public void sum() {
            if (param>0) {
                Eval e = new Eval (param-1);
                e.sum();
                rezult = param + e.rezult;
            }
            else rezult = 0;
        }
        private int param;
        public int rezult;

        Eval (int n) { param = n; }
    }

    public static void main(String[] args) {
        int n = 10000;
        //System.out.println("recursive : " + sumRec(n));
        Eval e = new Eval (n);
        e.sum();
        System.out.println("trampoline: " + e.rezult);
    }
}

Очевидные замечания, что можно посчитать в цикле или даже вспомнить Карла Фридриховича и посчитать в пару операций прошу не приводить - это все понятно, пример просто ради того, чтобы попробовать технологию, которая потом будет использоваться на нормальных задачах (если будет работать). Выдает переполнение стека. Собственно, почему? Объект через new каждый раз создается в куче, рекурсивных вызовов нет. На что расходуется стек текущего потока?

ЗЫ читал, что можно этот вычислительный объект создавать каждый раз в новом потоке, но мне хотелось бы остаться в одном. К тому же, я не знаю, как Java переварит миллион потоков, к примеру.


Ответ на: комментарий от anonymous
    public static class SumTask extends RecursiveTask<Integer> {
        private final int value;

        public SumTask(int value) { this.value = value;}

        @Override
        protected Integer compute() {
            return value>0 ? value + new SumTask(value - 1).fork().join() : 0;
        }
    }

Стековерфлоу уже при n=1000. Не хочет он создавать более 1000 новых форков, и я его понимаю. Фибоначчей каких или деревья параллельно суммировать наверное будет хорошо, но мою задачу как таковую я пока не представляю как с этим решить.

Ivana
() автор топика
Ответ на: комментарий от Ivana

Сделайте мемоизацию. И ради всего святого, используйте стандартные коллекции.

Weres ★★★
()
Ответ на: комментарий от Legioner

Подписываюсь под этим. Использовал рекурсию только для разбора древовидных штук типа категорий товаров или папок. Давно на курсах делали факториал с рекурсией, я решил переделать целиком подсчет в цикле и... стало в разы быстрее!

spoilt ★★★
()
Ответ на: комментарий от callbackhell

Но ведь в хаскеле нет настоящих продолжений. Стек все равно обязан раскрутится. Как же это может помочь в подобных вещах?

Монадической связке bind передается ни что иное, как продолжение. Дело разработчика - правильно распорядится этим продолжением. Монада Cont работает именно через продолжения, как они обычно понимаются в других языках.

А стек раскручивается по тому, что в монаде Cont все вычисления хвостово-рекурсивные. Поскольку в Haskell есть полноценная поддержка TCO, то вложенность рекурсии внутри вычисления Cont может быть огромной, пока хватит памяти.

Другой вопрос, что при малой вложенности скорость выполнения будет, ну раз в 100 медленнее, чем для обычных вычислений. Зато обычные вычисления валятся в переполнение стека при большой вложенности. Два варианта на выбор)

Забавный казус. В Scala есть продолжения, но нет полноценного TCO, что суживает круг применения ее продолжений.

dave ★★★★★
()
Ответ на: комментарий от dave

Дык, оптимизация ТСО и продолжения — это вещи ортогональные. ТСО — это разворачивание рекурсии в цикл, на уровне исполнителя. А продолжения, грубо говоря, срезают сегмент стека, не схлопывая его, и сохраняют в памяти.

callbackhell
()

Держи:

public class Main {

    private static int sumRec(int n) {return n>0 ? n + sumRec(n-1) : 0;}

    private static class Eval {
        public void sum() {
            if (param>0) {
                final Eval e = new Eval (param-1);
                if (param % 4999 == 0) {
                    Thread thread = new Thread(() -> {
                        e.sum();
                    });
                    thread.start();
                    try {
                        thread.join();
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                        return;
                    }
                } else {
                    e.sum();
                }

                rezult = param + e.rezult;
            }
            else rezult = 0;
        }
        private int param;
        public int rezult;

        Eval (int n) { param = n; }
    }

    public static void main(String[] args) {
        int n = 10000;
        //System.out.println("recursive : " + sumRec(n));
        Eval e = new Eval (n);
        e.sum();
        System.out.println("trampoline: " + e.rezult);
    }
}

4999 - взято экспериментальным путем и означает, что через каждые 5000 вложенных вызовов e#sum будет выделяться новый стек путем создания доп. треда.

foror ★★★★★
()
Ответ на: комментарий от foror

Вообще, лучше поставить число 4000 или меньше для стека в 1мб на дефолтной 64-bit jvm.

foror ★★★★★
()
Ответ на: комментарий от Ivana

Для каждого потока, внезапно, выделяется стек, поэтому если ты хочешь создавать кучу мелких потоков, то нужно крутить размер стека (при совсем мелком стеке скорее всего jvm вообще не запустится).

Для твоей задачи скорее всего подойдет банальный fork-join pool

maloi ★★★★★
()
Ответ на: комментарий от Ivana

Да сейчас прочитал внимательнее, надо выспавшимся отвечать. Сначала прочитал, что тебе нужно параллельно их запускать, а тебе наоборот нужно.

Еще прочитал остальные твои ответы в треде, перешел по ссылке с кодом на питоне. И где собственно там большая вложенность? Там рекурсивные только eval и read_from, но read_from никогда не должна упасть со StackOverflow на нормальном коде, а eval упадет только на вызове рекурсивной функции, но для решения этой проблемы нужно в интерпретаторе делать оптимизацию хвостовой рекурсию.

anonymous
()
Ответ на: комментарий от Ivana

Подожди, подожди. Какой стек оверфлоу?

более 1000 новых форков, и я его понимаю

Почему ты считаешь, что он не хочет создавать 1000 форков? Обрати внимание, таска != тред.

bytecode ★★
()
Последнее исправление: bytecode (всего исправлений: 1)
Ответ на: комментарий от Ivana

After some experience with functional languages

«Чувак с 78 плюсами» должен был пояснить малолетнему автору-долбоебу, что языки общего назначения (в т. ч. и жабка) не предназначены для написания программ в функциональном стиле: следует избегать рекурсии, лямбд, map-ов и прочей нездоровой в императивном контексте хуйни, потому что:

  • средний ремесленник не осилит
  • такой функциональный код вообще крайне тяжко поддерживать
  • коллеги могут подсобить по тупорылой морде, которая

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

И поскольку автор похож на безмозглого индуса (а я один раз в году помогаю безмозглому низшему существу), то вот как любая рекурсия переделывается в старый добрый цикл (прости, жаба многословна, как депутать перед выборами в госдуму, поэтому узри язык богов):

struct stack_frame {
  int param, rezult;
  struct stack_frame *prev;
  stack_frame(int param)
    : param(param)
    , rezult(0)
    , prev(nullptr) {}
  stack_frame(int param, stack_frame *prev)
    : param(param)
    , rezult(0)
    , prev(prev) {}
};

int eval(int param)
{
  stack_frame *sp = new stack_frame(param);
  /* "recurse": dynamically allocate new stack frame and set is as current */
  while (sp->param > 0)
      sp = new stack_frame(sp->param - 1, sp);
  sp = new stack_frame(0, sp); /* could just use ">=" condition in while loop */
  /* "return" from recursion */
  while (sp->prev) {
    stack_frame *caller = sp->prev;
    caller->rezult = caller->param + sp->rezult;
    delete sp; /* вот этого в жабке делать не надо, мудрый gc подотрет за тобой говно */
  }
  return sp->rezult; /* здесь утекает память, но ты пишешь на жабке, поэтому не заморачивайся */
}

Можете обосрать, код действительно говно (особенно когда param < 0), но показать на примере концевой рекурсии более общие приемы без лишнего кода, который ввергнет типичного индуса в многодневную фрустрацию, крайне проблематично.

anonymous
()
Ответ на: комментарий от bytecode

Да там действительно будет StackOverflow. Я сразу написал, что в том случае не применимо, просто написал для примера. Там по сути все выполнится на одном потоке (если не ошибаюсь) и упадет еще раньше, но в более общем случае, когда задача запускает несколько задач все хорошо растечется по потокам и проблем не будет. Просто я не до конца проснувшись как-то прочитал в оп, что ему нужно параллельно (а для этого ничего лучше FJP нет в Java), но потом перечитал и понял, что наоборот нужно на одном потоке. А потом прочитал следующие посты и понял, что вообще не понимаю при чем тут рекурсия, если проблема вообще не совсем в ней. Если алгоритм такой же, как по ссылке, то там единственные места, где может быть StackOverflow - eval рекурсивной функции, но это решается добавлением TCO в интерпретатор, и read_from на неадекватном коде (с более N тысяч вложенных выражений, чтобы хватило на переполнение стека).

anonymous
()
Ответ на: комментарий от DiKeert

Да, широк спектр отвечающих - от открывших для себя итеративный факториал до гур с понтами ) Но спасибо всем, это лучше, чем если бы форум был мертвый. Предпоследний кот - это похоже на озвученную на той странице идею запускать новый тред по джойну когда уже пора. Причем это пора можно считать по условному числу итераций, а можно наверное делать тестовое выделение чего-то на стеке, и ловить стековерфлоу эксепшн, если поймали, то новый тред. А последний кот вообще со звездами (не только в переносном смысле), а мне вроде говорили что звезд в джаве нет. Попробую конечно, спасибо.

ЗЫ и, поскольку я как бы в курсе, что любую рекурсию можно написать итеративно, не оставляю вариант допилить мою итеративную реализацию.

Ivana
() автор топика
Ответ на: комментарий от Ivana

А ты можешь дать конкретный код (Lisp), который вызывает StackOverflow? Реализация интерпретатора у тебя такая же, как по ссылке, которую ты дал? Понятно, что оно в хаскеле работает, там с его ленивостью и оптимизациями рекурсии нужно сильно постараться, чтобы его рекурсией уронить.

anonymous
()
Ответ на: комментарий от foror

Да, с созданием нового треда каждые 2000 итераций (больше мой стек не тянет) считает и миллион. 500 тредов. Нормально. Но как применить этот принцип к рекурсии общего вида пока есть непонятные моменты.

Ivana
() автор топика
Ответ на: комментарий от anonymous

Пример - запросто.

(defn list-from-to (a b)
    (defn go (i) (cond (<= i b) (cons i (go (+ i 1))) True nil))
    (go a))
И мой эвал честно рекурсивно вызывает эти консы, но на списке длиной 400 уже валится. Повторюсь, если не выделить стека побольше. То есть я даже не могу в памяти создать строгий список от 1 до 400.

Ivana
() автор топика
Ответ на: комментарий от Ivana

Выше пару раз звучало предложение сделать ТСО прямо в интерпретаторе. Более того, об этом пишет и Норвиг вот здесь (и про few hundred iterations в точку) http://norvig.com/lispy2.html

Better eval with tail recursion optimization

Scheme has no while or for loops, relying on recursion for iteration. That makes the language simple, but there is a potential problem: if every recursive call grows the runtime stack, then the depth of recursion, and hence the ability to loop, will be limited. In some implementations the limit will be as small as a few hundred iterations. This limitation can be lifted by altering eval so that it does not grow the stack on all recursive calls--only when necessary.

Но есть 2 проблемы ) 1) надо осилить ТСО при интерпретации 2) даже если осилю - вышеприведенный пример лисп-кода отнюдь не ТСО, и он также будет валиться. Его можно переписать через ТСО снакоплением списка в аккумуляторе, но мне хотелось чтобы работали и такие варианты, без ТСО.

Ivana
() автор топика
Ответ на: комментарий от Ivana

Ну то есть как я и сказал выше - проблема не в рекурсии, проблема в том, что интерпретатор сам по себе не оптимизирует рекурсивные вызовы. Точно такую же проблему ты получишь в абсолютно любом императивном языке. Сделай в интерпретаторе мемоизацию вычислений, оптимизацию хвостовой рекурсии и переделай свою функцию в хвостовую, например как-то так:

(defn list-from-to (a b)
    (defn go-tc (i l) (cond (<= i b) (go-tc (+ i 1) (cons l i)) True l))
    (defn go (i) (cond (<= i b) (go-tc (+ i 1) i) True nil))
    (go a))

anonymous
()

Не эксперт. Что мешает переписать с использованием циклов? Вообще, зачем создавать тысячи объектов, это точно необходимо в вашей задаче?

gh0stwizard ★★★★★
()
Ответ на: комментарий от anonymous

Да, есть такая буква, выше об этом как раз и писал. Можно пойти еще дальше (как в Clojure), и все хвосторекурсивные функции объявлять с префиксом recur, чтобы интерпретатор не разбирался а сразу верил, что тут можно вычислять в цикле. А исходный мой вариант не ТСО объявить хреновым и оставить так как есть, типа юзер сам дурак раз такое пишет :) Тоже вариант, но все-таки хотелось быть лояльнее к юзерским вариантам. Хаскель расслабил, наверное )

Ivana
() автор топика
Ответ на: комментарий от gh0stwizard

Что мешает переписать с использованием циклов?

Неочевидность и для меня нетривиальность разворачивания рекурсивной функции в итеративный цикл. Да, про факториал знаю, не надо предлагать )

Вообще, зачем создавать тысячи объектов, это точно необходимо в вашей задаче?

Юзер напишет кот итерации по списку объектов... А их всего 400 максимум... Обидно. У меня список простых чисел например валится если запросить больше этого диапазона. В общем, надо.

Ivana
() автор топика
Ответ на: комментарий от Ivana

Неочевидность и для меня нетривиальность разворачивания рекурсивной функции в итеративный цикл. Да, про факториал знаю, не надо предлагать )

Ну, твое дело. Костыли тебе уже предложили. Просто это «правильный» (как мне говорили) путь решать задачи исключив stack overflow, так сказать, by design. На куче у тебя может быть heap overflow или еще что-то. Ну и с потоками что-то приключится также может. А циклы? Олдскул? Не, не понимаем.

Пробуй все варианты. Может что найдешь под свою задачу.

gh0stwizard ★★★★★
()
Ответ на: комментарий от Ivana

Если задача одноразовая или весь смысл программы в этом рассчете, то самое простое решение - самое лучшее. Дать ей побольше стэка и не морочить голову =). Потом уже, когда понятно станет, что этого мало можно FJP туда пихать или итеративное нечто.

DiKeert ★★
()
Ответ на: комментарий от gh0stwizard

Кстати, на счет потоков тоже отличное замечание, 10к problem никто не отменял =)

DiKeert ★★
()
Ответ на: комментарий от Ivana

Ну если у тебя побочных эффектов в lisp коде нет, то ты можешь сделать так: 1. Если встречаешь рекурсивный вызов - помечаешь его особо 2. При eval функции с рекурсивным вызовом возвращай не значение, а целиком выражение с вызовом 3. В цикле вызывай eval пока не получишь конкретное значение

anonymous
()
Ответ на: комментарий от Ivana

(все тот же анонимус) В продолжение к предыдущему моему комменту - в случае бесконечной рекурсии ты в этом случае получишь бесконечный цикл или OOM, если выражение на каждой итерации увеличивается. И, очевидно, что для этого нужно не просто текстом выражение хранить, тк на последней итерации (list-from-to 1 400) у тебя будет выражение из 403 токенов, так что лучше их как-то пооптимальнее хранить, где возможно.

anonymous
()
Ответ на: комментарий от anonymous

Хм. Все мои потуги перенести вычисления в кучу основаны на том, что у меня без захвата стека итеративно в куче сначала разворачивается мое выражение из 403 токенов-консов, а потом оно же сворачивается обратно в мой список из 403 оберток над интами. То есть, то же самое, что происходит в стеке, будет происходить в самом объекте. Но ведь куча же намного больше дефолтного стека? Если ее тоже легко забить, то может и правда нуегонафиг такие сложности, и проще стека добавить и может ТСО в интерпретатор влепить да и все?...

Ivana
() автор топика
Ответ на: комментарий от Ivana

ЗЫ спасибо всем отвечающим, если я не реагирую, то это не значит что я не читаю - просто мне пока нечего сказать, надо думать и пробовать. Единственно что не хочется - через титанические усилия переписать итеративно или через потоки и обнаружить, что это помогает лишь в отдельных случаях )

Ivana
() автор топика
Ответ на: комментарий от Ivana

Да, намного больше. 403 токенами ты её не забьешь, даже миллионом не забьешь, если у тебя не совсем ограниченная куча. Просто на всякий случай сказал, чтобы потом не говорил, что на определенном кол-ве элементов оно с ООМ падает.

anonymous
()
Ответ на: комментарий от anonymous

Ну и памяти, очевидно, потребуется в 2 раза больше, чем на рекурсивно-хвостовой алгоритм, даже если это реализовать так.

anonymous
()
Ответ на: комментарий от anonymous

Ну если кучу миллионом не забью, то и хрен с ним что в 2 раза памяти больше потребуется и времени 2 пробежки вместо одной - всяко не фью хандредз итерейшнс. Ладно, идей подкинули много, пойду переваривать и пробовать что получится.

Ivana
() автор топика
Ответ на: комментарий от Ivana

По методу создания нового потока каждые 500 вызовов eval-а на дефолтном стеке функция

(defn f (n) (cond (= n 0) 0 (+ n (f (- n 1))) ))
(как видно, нехвостовая рекурсия) вычисляется для n = 300000 создавая 7000 (или раза в 2 больше, непонятно пока) потоков, при бОльших значениях n - java.lang.OutOfMemoryError: unable to create new native thread. Без разделения на потоки при дефолтном стеке уже при n = 400 java.lang.StackOverflowError. В одном потоке при -Xss64M (больше стека не дает отрезать) - работает до n = 60000. Нормальный результат, ящетаю. Хотя, с другой стороны, не настолько фатальный прирост по сравнению с увеличением стека. Спасибо еще раз за идеи. Буду шлифовать дальше.

Ivana
() автор топика

Выражение e.sum приводит к вычислениям сразу же, т.е. сразу к полной рекурсии по всем элементам. Фактически, здесь отложенность только на один шаг.

В результате имеем: e.sum(10000) приводит сразу к необходимости вызвать e.sum(9999) а она в свою очередь вызывает e.sum(9998) и т.д.

На это расходуется стек текущего потока. А тебе нужно я так понимаю, чтоб в куче сначала насоздавалось много всего.

anonymous
()
Ответ на: комментарий от anonymous

Разбей sum на две части - первая только создаёт в куче элементы Eval(10000), Eval(9999),... а вторая часть сворачивает их в число.

anonymous
()
Ответ на: комментарий от anonymous

И таки да - это по сути лишь попытка перевести нехвостовую рекурсию в хвостовую. На жабе может и не дать желаемого результата.

anonymous
()
Ответ на: комментарий от callbackhell

Дык, оптимизация ТСО и продолжения — это вещи ортогональные. ТСО — это разворачивание рекурсии в цикл, на уровне исполнителя. А продолжения, грубо говоря, срезают сегмент стека, не схлопывая его, и сохраняют в памяти.

Продолжения - многозначный в программировании термин. Описанное мною относится к «continuation passing style» (CPS), а CPS как раз таки связано с TCO. Без TCO практический смысл в CPS резко падает. Просто при CPS все вызовы получаются хвостовыми.

А монады при желании разработчика могут поддерживать CPS, потому что, как я написал выше, в монадическую связку (>>=) (она же известна как Bind, flatMap, SelectMany) передается ни что иное, как продолжение вычисления. Другое дело, что большинство монад не следуют CPS. Монада Cont из Haskell и монада async workflow из F# следуют. Потому там можно делать рекурсивные вызовы огромной вложенности, и стек действительно уходит в кучу.

Впрочем, я уже повторяюсь здесь, и мы ходим кругами.

dave ★★★★★
()
Ответ на: комментарий от dave

Вести с полей - почти реализовал до конца итеративное мутабельное разрушающее вычисление - исходное выражение заменяется на вычисленное прямо по ссылке объекта, типа (+ 1 2 (+ 3 4)) трансформируется прямо на месте сначала в (+ 1 2 7) и потом в 10, аппликативный порядок лямбда-редукции во всей красе, но с функциями получилось не очень хорошо - каждый вложенный вызов приходится копировать полностью структуру тела функции и вычислять (редуцировать) на месте эту копию (оригинал нельзя - затрется). Стек самодельный, при хвостовых вызовах тоже растет, но хоть объект не разрастается. Но в общем все равно все это медленно и нестабильно, баги трудноотлавливаемые полезли...

И тут я вспомнил то, что в хаскельной реализации было бесполезным - я сравнивал по скорости с рекурсивным вариантом и выигрыша не было, поэтому я выпилил это, но в этом царстве императивного ужаса оказалось весьма кстати - встречайте особую форму while:

(def l nil n 1000000)
(while (> n 0) (set! l (cons n l)) (set! n (- n 1)))
(def s 0)
(while (not (null? l)) (set! s (+' s (car l))) (set! l (cdr l)))
s

Реализация в коде - одна (!) строка, а эффект - при дефолтном стеке и список на миллион разворачивается в куче, и сворачивается как угодно. Торжество императивных циклических алгоритмов и изменяемого состояния. Конечно это далеко не ТСО, но за одну строку реализации можно себе позволить. А полностью красиво решать задачу переноса стека в кучу я ниасилю наверное. Действительно, стека при старте нарезать проще, ну может еще ТСО в интерпретатор добавлю, если осилю, и хорош.

Ivana
() автор топика
Ответ на: комментарий от Ivana

TCO, кстати, можно сделать через «трамплин». В Scala хотели было сделать через него, даже следы были в Scala API, но потом одумались. А для интерпретатора почему бы и нет?

dave ★★★★★
()
Ответ на: комментарий от dave

Я хочу попробовать сделать ТСО, буду читать как там у Норвига сделано, но пока честно говоря я не очень представляю себе даже на уровне идеи. Даже если оставить в стороне вопрос как интерпретатор узнает что эта функция с терминальным вызовом (хотя это отдельный вопрос, можно как в Clojure писать костыльные аннотации для этого, но не суть). Если не трудно, не могли бы поподробнее для индусов описать саму идею?

Ivana
() автор топика
Ответ на: комментарий от Ivana

Однако, работает у меня ТСО, даже перекрестная вроде, варианты функций без нее переполняют стек:

(defn f (n) (cond (= n 0) 0 (+ n (f (- n 1))) ))
(defn g (n a) (cond (= n 0) a (rec g (- n 1) (+ n a)) ))

(defn is-even (n) (cond (= n 0) True  (is-odd  (- n 1)) ))
(defn is-odd  (n) (cond (= n 0) False (is-even (- n 1)) ))

(defn is-even-tco (n) (cond (= n 0) True  (rec is-odd-tco  (- n 1)) ))
(defn is-odd-tco  (n) (cond (= n 0) False (rec is-even-tco (- n 1)) ))

Сейчас терминальный вызов сделан через особую форму rec, которая возвращает объект-пару функция/аргументы, и при вызове функции если она возвращает этот объект, то крутится цикл пока не возвратит что-то другое. По-моему тут выше в топике предлагалась такая идея - спасибо за наводку. Может вообще получится все вызовы переписать на возвращение этого промежуточного объекта, чтобы хвостовые вызовы определялись автоматически, без дополнительной особой формы. Но тогда наверное для нетерминальных вызовов стек еще в 2 раза сократится за счет промежуточных вызовов эвалов этих объектов.

Ivana
() автор топика
Ответ на: комментарий от Ivana

У Норвига? PAIP? Там что-то вроде своей стековой машины и такое специальное соглашение о вызовах для нее, что TCO получается сам собой. Книга толстая, и сам подход описан в самом конце книги. Если знаком с Common Lisp, то, наверное, можно попробовать прочитать только главы 22 и 23, особенно последнюю.

А метода трамплина, кажется, описан в википедии, да еще где-то встречал.

https://en.wikipedia.org/wiki/Tail_call#Implementation_methods

dave ★★★★★
()
Ответ на: комментарий от Ivana

Есть какие-нибудь не совсем страшные технологии, позволяющие решить эту задачу?

TCO на жабке распишешь без стека но с кучей строк за 50.

yyk ★★★★★
()
Ответ на: комментарий от yyk

Изначально планы были наполеоновские, не только ТСО, а любую нелинейную рекурсию вынести в кучу или в потоки. ТСО то я уже сделал в самом интерпретаторе, а не хвостовую рекурсию красиво не победил, хотя были реализованы 2 варианта разной степени рабочести - через вложенные потоки и через перепись в итеративный алгоритм. В текущей версии останусь на обычной однопоточной рекурсии и при необходимости просто буду запрашивать стек побольше.

Ivana
() автор топика
Ответ на: комментарий от Ivana

сорь, я не правильно выразился: просто TC реализовать не сложно, без O :) Пользоваться, правда, в жабке не очень удобно, но если очень надо...

P.S. Не сложно - не значит дешево :)

yyk ★★★★★
()
Ответ на: комментарий от yyk

Я может что-то не понимаю, но просто ТС без О - это я ))) (шучу). Просто ТС без О - это обычные рекурсивные вызовы, с сохранением предыдущего контекста и созданием нового, с новыми значениями связанные переменных - аргументов функции. И на это расходуется стек точно так же, как и при любых других С, не только Т. И всё О заключается в том, чтобы вместо этого крутить вызовы в цикле до победного конца в одном контексте, затирая старые значения аргументов новыми - старые при ТС никому не нужны. Ну во всяком случае я у себя в интерпретаторе реализовал так, и работает вполне даже О ).

Ivana
() автор топика
Ответ на: комментарий от Ivana

ЗЫ сейчас бодаюсь с интерфейсом в Swinge-е, как будет что-то более менее работоспособное - выложу, если будет интересно.

Ivana
() автор топика
Ответ на: комментарий от Ivana

Просто ТС без О - это обычные рекурсивные вызовы, с сохранением предыдущего контекста и созданием нового, с новыми значениями связанные переменных - аргументов функции. И на это расходуется стек точно так же, как и при любых других С, не только Т. И всё О заключается в том, чтобы вместо этого крутить вызовы в цикле до победного конца в одном контексте, затирая старые значения аргументов новыми - старые при ТС никому не нужны.

Отлично! Теперь для экономии стека тебе надо сохранить «связанные переменные» в объект и вернуть не результат, а нечто для вызова нужного тебе кода с этими самыми данными. Понятно - весь код надо перелопатить. И да, вместо простого вызова - цикл: если возвращен «результат» - готово!, если «оная структура аля TC» - «вызов» её и го в начало цикла :)

yyk ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.