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 переварит миллион потоков, к примеру.


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

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

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

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

Это создание нового объекта в куче, и вызов его метода (который без параметров), и я считал, что тут нечего сохранять в стек.

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

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

Экземпляр класса в любом случае будет лежать в куче, это ж не кресты, тут объекты на стеке не выделяются.

no-such-file ★★★★★
()

как Java переварит миллион потоков, к примеру.

А Java ЕМНИП использует нейтив ОС треды, поэтому, она переварит приблизительно столько, сколько переварит ось на которой она работает.

callbackhell
()
Ответ на: комментарий от no-such-file

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

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

А чем цикл не подходит? Или просто функция с вызовом самой себя в конце, это хвостовая рекурсия и она соптимизируется.

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

Или просто функция с вызовом самой себя в конце, это хвостовая рекурсия и она соптимизируется.

А разве жаба гарантирует оптимизацию TCO?

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

С хвостовой я бы сам все развернул в цикл и не полез бы постигать такие дебри. У меня не хвостовая. Вот тут http://stackoverflow.com/questions/860550/stack-overflows-from-deep-recursion-in-java чувак с 78 плюсами за свой пост кое-что объясняет и предлагает, но у меня пока не хватает ума понять, поэтому пробую своими костылями

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

не хватает ума понять

Он там предлагает не вызывать ничего рекурсивно, а сделать стек данных на коленке, который потом обходить линейно в обычном for.

no-such-file ★★★★★
()
Ответ на: комментарий от dyb4hzvo

А чего надеяться, проверить пару секунд

private static int sumRecAcc(int n, int a) {
        return n>0 ? sumRecAcc(n-1, n+a) : a;}
10000 вызовов тянет, 50000 уже стековерфлоу. Но такое как раз просто в цикл развернуть самому.

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

Понятно. Я надеялся, что цепочка связанных объектов в куче как раз и будет таким моим стеком на коленке, вместо системного. буду пробовать костылить его как-то еще.

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

Чтобы стек ушел в кучу, нужны продолжения и оптимизация хвостовой рекурсии, причем, косвенной хвостовой рекурсии. Этого нет даже в Scala, тем более, нет в Java.

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

А вот с помощью async workflow ты мог бы это сделать на F#. Тоже самое можно проделать с помощью монады Cont на Haskell. Тогда стек уйдет в кучу.

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

Вести с полей - если тупо создавать новый поток на каждый вложенный вызов, то при 10000 ошибка java.lang.OutOfMemoryError: unable to create new native thread Но это в любом случае был плохой вариант. Хотя бы уж создавать тогда, когда в текущем потоке уже стековерфлоу эксепшен, чтобы не разбрасываться потоками. Да и на андроиде еще надо посмотреть что с потоками будет...

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

Ну я же говорю, жаба зависима о ос-потоков, там нет нормальной конкурентности.

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

На Haskell у меня все и без Count хорошо ) Это я просто хотел написать (и уже в общем написал) GUI РЕПЛ Лиспа, хотел для Андроида, но поскольку Java не знаю, начал с десктопа. Все работает, только надо заранее увеличить стек потока перед запуском. Вот и хотел чтобы совсем по фен-шую, перенести в кучу. А Лисповский Эвал - это весьма не хвосторекурсивная функция. Я наваял итеративный вариант вычисления вложенных S-выражений, но он полностью строгий - вычисляются все поля, а мне надо в зависимости от головы списка дальше часть не вычислять. Вот и решил зайти с другой стороны - придумать универсальный механизм реализации рекурсивных функций в малом стеке.

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

Поищи либу какую-нть, которая реализует треды на уровне яп, может есть такая.

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

Это не простой способ. Можно и байткод вытащить из метода и интерпретировать его на своём стеке :) Но лучше избавиться от рекурсии. В Java-коде её лучше не применять, если только не уверен на 100%, что итераций будет немного.

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

с помощью монады Cont

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

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

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

ЗЫ мне Java не впилась, но хотел под Андроид, как уже говорил )

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

Тогда радикальный вопрос - а если не Java? Но под Андроид :) Или это в принципе не решаемая на JVM проблема? А то Cotlin хвалили, также F# с Xamarin (хотя с оговоркой, что он платный). Выше писали, что на F# есть способ решить проблему, он портируется потом на JVM и Андроид? Или только в экзешнике F# будет работать?

ЗЫ или не выпендриваться и попытаться добить мой итеративный эвалюатор. Или совсем не выпендриваться и оставить все рекурсивно как есть, и запрашивать стек при старте...

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

Kotlin работает точно так же, как и Java, он тебе точно не поможет.

ЗЫ или не выпендриваться и попытаться добить мой итеративный эвалюатор. Или совсем не выпендриваться и оставить все рекурсивно как есть, и запрашивать стек при старте...

А что ты пытаешься сделать, что так сложно реализовать без языковой рекурсии? У тебя есть реальная потребность в рекурсии, или просто не хватает TCO? Если реальная потребность, то системный стек это довольно экономная штука, в целом. Если тебе его не хватает, то при наивной реализации через стандартные структуры данных у тебя потребность в памяти может раздуться раз в 10, т.е. если тебе не хватает стека в 8 мегабайтов, то после переноса на кучу у тебя может уйти и 100 мегабайтов, а на мобильном это может быть непозволительная роскошь.

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

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

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

Я пытаюсь свою игрушку - Лисп интерпретер сделать с GUI и портировать на Андроид. Собственно функция - по сути типа evalиз этой ссылки http://habrahabr.ru/post/115206/, то есть рекурсивная донельзя, и весьма не хвостовым образом (а в моем случае еще наворченнее). ТСО меня, полагаю, не спасет. А вот аргумент, что при собственном велосипедении стека будет в несколько раз затратнее по памяти и раз в n упадет скорость и так небыстрой интерпретации, это уже весомо...

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

ITT быдлокодер считающей себя элиткой неосиливает Java. Хиханьки.

anonymous
()

Посмотри в сторону ForkJoinPool/ForkJoinTask. У меня есть подозрение, что это то что тебе нужно.

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

при собственном велосипедении стека будет в несколько раз затратнее по памяти и раз в n упадет скорость и так небыстрой интерпретации, это уже весомо...

Это при наивной реализации. Для максимальной экономии надо использовать один массив байтов (или несколько связанных, идущих «один за другим») и самостоятельно упаковывать аргументы на этот массив (int в 4 байта, например). Тогда расходы на хранение всякой служебной информации будут минимальными. А если ArrayList<MyStackFrame> использовать, тут уже будет хуже.

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

оп твоей ссылке

красота языка заключается в том, что нам необходимо всего лишь 6 специальных форм

на вскидку, хоть я и не пишу на scheme, вспоминаются define set! let letrec set-list! set-vector! lambda if cond or and — уже больше чем 6, ясно, что это далеко не все. Конечно, часть можно определить через макросы, но макросы тоже требуют своих спецформ, так что, в 6 явно не уложимся. Или я что-то не так понял.

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

ForkJoinPool/ForkJoinTask обязательно посмотрю что это такое, спасибо.

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

Вот твой пример:

public class SumTask extends RecursiveTask<Integer> {
    private final int value;
    
    public SumTask(int value) {
        this.value = value;
    }

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

    public static void main(String[] args) {
        new ForkJoinPool().invoke(new SumTask(10000));
    }
}
В данном случае, конечно, из пушки по воробьям, но по-моему то, что тебе нужно.

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

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

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

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

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

Исходники Kawa и Clojure боюсь навскидку не осилю... Хотел такой пет-прожект, без претензий.

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

anonymous, спасибо, буду разбирать ваш пример.

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

плюс мои макросы рантаймовые

Это называется fexpr's в терминах лиспа, или ленивые функции. Рантаймовые макросы — это оксюморон.

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

и я считал, что тут нечего сохранять в стек.

По идее указатель на объект в куче и адрес возврата из текущей функции

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

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

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

это я про рантаймовые макросы и оксюмороны

Я понял. Не помню тот тред, но те кто с этим спорили — просто дебилы.

тутвсе подряд идет, не знаю как цитировать

вот так>> или заключаешь в теги quote как обычно. В стартовых постах работает только второе.

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

в newlisp фекспры называются макросами, но там в документации есть пояснение, что это не макросы. В остальном — это просто безграмотность.

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

я в стане дебилов, есличо ) И пор оппонентов мнение аналогичное ) Но это здесь оффтоп в любом случае.

Пробую играться с ForkJoinPool.

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

Тред не читал, но отвечу (это же ЛОР). ForkJoinPool - не решение сабжа как самоцели, но для параллелизации вычислений подходит просто прекрасно. Реализация под капотом очень умная и оптимизированная, такими возможностями грех не воспользоваться. Только боксинга избегай, были тут «случаи».

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