Как предсказать максимальную глубину вызова рекурсивного метода?

В целях оценки максимальной глубины вызова, которую может достичь рекурсивный метод с заданным объемом памяти, какова (приблизительная) формула для вычисления памяти, используемой до ошибки, вероятно, произойдет?

Edit:

Многие ответили "это зависит", что разумно, поэтому позвольте удалить некоторые переменные, используя тривиальный, но конкретный пример:

public static int sumOneToN(int n) {
    return n < 2 ? 1 : n + sumOneToN(n - 1);
}

Легко показать, что запуск этого в моей Eclipse IDE взрывается для n чуть меньше 1000 (удивительно низкий для меня). Возможно ли, что этот предел глубины вызова был оценен без его выполнения?

Изменить: я не могу не думать о том, что Eclipse имеет фиксированную максимальную глубину вызова 1000, потому что я дошел до 998, но там один для основного и один для начального вызова метода, делая 1000 во всех. Это "слишком круто" число ИМХО, чтобы быть совпадением. Я буду исследовать дальше. У меня только Dux накладные расходы -Xss vm параметр; это максимальный размер стека, поэтому бегун Eclipse должен иметь -Xss1000 установить где-нибудь

Ответ 1

Это явно JVM- и, возможно, также специфично для архитектуры.

Я измерил следующее:

  static int i = 0;
  public static void rec0() {
      i++;
      rec0();
  }

  public static void main(String[] args) {
      ...
      try {
          i = 0; rec0();
      } catch (StackOverflowError e) {
          System.out.println(i);
      }
      ...
  }

используя

Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)

работает на x86.

С 20 МБ стек Java (-Xss20m) амортизированная стоимость колебалась около 16-17 байт за вызов. Самый низкий показатель, который я видел, был 16,15 байт/фрейм. Поэтому я заключаю, что стоимость составляет 16 байт, а остальные - другие (фиксированные) служебные данные.

Функция, которая принимает один int, имеет в основном одну и ту же стоимость, 16 байт/фрейм.

Интересно, что для функции, которая принимает десять ints, требуется 32 байт/кадр. Я не уверен, почему стоимость настолько низкая.

Приведенные выше результаты применяются после компиляции кода JIT. До компиляции стоимость каждого кадра намного выше. Я еще не понял способ достоверно оценить его. Однако это означает, что у вас нет надежды надежно предсказать максимальную глубину рекурсии, пока вы не сможете надежно предсказать, была ли рекурсивная функция скомпилирована JIT.

Все это было протестировано с размерами стека ulimit размером 128 Кбайт и 8 МБ. Результаты были одинаковыми в обоих случаях.

Ответ 2

Только частичный ответ: от JVM Spec 7, 2.5.2, стеки кадров могут быть выделены в куче, а размер стека может быть динамичным. Я не мог сказать наверняка, но кажется, что размер стека может быть ограничен только вашим размером кучи:

Поскольку стек виртуальной машины Java никогда не управляется напрямую, кроме как для push и pop frames, фреймы могут быть выделены в кучу.

и

Эта спецификация позволяет стекам виртуальной машины Java либо быть фиксированный размер или динамическое расширение и списание в соответствии с требованиями вычисление. Если стеки виртуальной машины Java имеют фиксированный размер, можно выбрать размер каждого стека виртуальной машины Java независимо от того, когда этот стек создан.

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

Итак, это будет реализация JVM.

Ответ 3

Ответ: все зависит.

Прежде всего, размер Java Stack можно изменить.

Во-вторых, размер кадра стека данного метода может варьироваться в зависимости от разных переменных. Более подробную информацию можно найти в разделе "Размер кадра стека вызовов" "Стек вызовов" - Википедия.

Ответ 4

Ответ Addig на NPE:

Максимальная глубина стека кажется гибкой. Следующая тестовая программа печатает значительно разные номера:

public class StackDepthTest {
    static int i = 0;

    public static void main(String[] args) throws Throwable {
        for(int i=0; i<10; ++i){
            testInstance();
        }
    }

    public static void testInstance() {
        StackDepthTest sdt = new StackDepthTest();
        try {
            i=0;
            sdt.instanceCall();
        } catch(StackOverflowError e){}
        System.out.println(i);
    }

    public void instanceCall(){
        ++i;
        instanceCall();
    }
}

Вывод:

10825
10825
59538
59538
59538
59538
59538
59538
59538
59538

Я использовал значение по умолчанию для этой JRE:

java version "1.7.0_09"
OpenJDK Runtime Environment (IcedTea7 2.3.3) (7u9-2.3.3-0ubuntu1~12.04.1)
OpenJDK 64-Bit Server VM (build 23.2-b09, mixed mode)

Итак, вывод: если у вас гной было достаточно (т.е. более двух раз), у вас есть второй шанс; -)

Ответ 5

зависит от вашей системной архитектуры (32 или 64-разрядные адреса), количества локальных переменных и параметров метода. Если хвостовое рекурсивно не накладные расходы, если компилятор оптимизирует его в цикле.

Вы видите, нет простого ответа.