Почему F # накладывает низкий предел на размер стека? - PullRequest
15 голосов
/ 31 октября 2011

Я хотел бы знать, есть ли фундаментальная причина для ограничения глубины рекурсии в F # до 10000 или около того, и в идеале, как избежать этого предела.Я думаю, что совершенно разумно писать код, который использует пространство стека O (n), и был бы благодарен, если кто-то, кто не согласен, мог бы объяснить, почему они это делают.Большое спасибо.Я объясню свое мышление ниже.

Я не вижу никаких причин, по которым стек нельзя увеличивать до тех пор, пока не будет исчерпана вся доступная память.Это означало бы, что бесконечная рекурсия заметит больше времени, но это не так, как если бы мы уже не могли писать программы, которые потребляют бесконечный объем памяти.Я знаю, что можно сократить использование стека до O (1), используя продолжения и хвостовую рекурсию, но я не особо понимаю, как это хорошо для меня, чтобы делать это постоянно.Также я не вижу, как полезно знать, когда функции, вероятно, потребуется обрабатывать «большой» вход (ну, по стандартам 8-битного микроконтроллера).

Я думаю, что этопринципиально отличается от необходимости, например, использовать накапливающиеся параметры, чтобы избежать квадратичного поведения по времени.Хотя это также включает в себя заботу о деталях реализации и не нужно делать это для «маленьких» входных данных, оно также сильно отличается тем, что компилятор не может самостоятельно устранить проблему самостоятельно.Кроме того, он отличается в этом немного сложном коде O (n), который был бы O (n ^ 2), если бы он был написан наивно, значительно полезнее, чем простая, медленная и легкая для чтения версия.Напротив, код в стиле продолжения имеет ту же сложность памяти, что и соответствующая наивная версия, но просто использует другой тип памяти.Это такая вещь, о которой компилятор не должен заставлять меня беспокоиться в наше время?

Хотя я бы «предпочел» теоретическую причину, по которой невозможно иметь глубокий стек, мы могли быа также обсудить практические аспекты.Мне кажется, что стек является несколько более эффективным способом управления памятью, чем куча, поскольку он не требует сборки мусора и легко освобождается?Я не уверен, что могу даже увидеть, что есть возможность делать глубокие стеки.Следует признать, что ОС необходимо выделить достаточно виртуального пространства, чтобы вместить всю память, которую вы, возможно, захотите использовать сразу во всей программе для стека каждого потока.Ну и что.Это не значит, что мы, вероятно, выйдем за пределы общепринятого в настоящее время 48-битного предела, или производители оборудования не смогут тривиально увеличить этот предел до 64-бит?

Не так уж много специфического для F #Вот.Я ожидаю, что такое же ограничение применяется в C #, и не вижу, что оно там больше необходимо, хотя на практике это очевидно намного меньше боли при программировании в императивном стиле.

Большое спасибо за любыеответы / комментарии.

РЕДАКТИРОВАТЬ: я написал резюме ответов ниже.

Ответы [ 6 ]

9 голосов
/ 02 ноября 2011

Наиболее убедительной причиной того, что F # наследует ограничения .NET в этом контексте, является совместимость.Компиляторы могут полностью исключить стек, например, компилятор SML / NJ для Standard ML автоматически преобразует программы в стиль продолжения.Два основных недостатка заключаются в том, что требуется глобальное изменение в соглашении о вызовах, которое нарушает совместимость и что оно существенно менее эффективно.Если бы F # сделал это, чтобы избежать переполнения стека, то совместимость C # была бы намного сложнее, а F # - намного медленнее.

Еще одна причина, по которой глубокие стеки являются плохой идеей, - сборщик мусора.Стеки обрабатываются GC специально, потому что они гарантированно являются локальными и могут сжиматься без необходимости сбора..NET GC обходит все стеки потоков, когда какой-либо поток создает коллекцию gen0.Следовательно, наличие только двух спящих потоков с глубокими стеками может замедлить работу другого потока в 10 раз.Представьте, насколько хуже это будет с гораздо более глубокими стеками.Эту проблему можно решить, изменив способ обработки GC стеков, по сути, превратив их в кучи, но это значительно замедляет операции со стеками.

8 голосов
/ 31 октября 2011

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

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

Это такая вещь, о которой компилятор не должен заставлять меня беспокоиться в наше время?

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

Обратите внимание, что, в частности, в F # вы можете использовать рабочий процесс CPS, который преобразует значительную часть стека в вызовы кучи и / или хвоста, с относительно небольшим изменением стиля / синтаксиса программирования, если вы хотите перейти туда ( см. например здесь ).

6 голосов
/ 31 октября 2011

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

3 голосов
/ 31 октября 2011

Я могу представить себе как минимум две возможные причины:

(1) На многих компьютерных архитектурах трудно увеличить размер стека во время выполнения, не перемещая его куда-либо еще в адресном пространстве.Как отметил @svick в комментариях, .NET в 32-битной Windows ограничивает размер стека основного потока до 1 МБ.Изменение размера стека основного потока в Windows требует изменения файла .EXE.

(2) Это FAR, FAR чаще встречается из-за переполнения стека из-за ошибки программирования, чем из-за фактического наличия кодадействительно должен превышать доступный размер стека.Ограниченный размер стека очень полезен для выявления ошибок программирования.В 98% случаев, если стеку разрешено увеличиваться до доступной памяти, вы впервые узнаете об ошибках программирования, когда исчерпаете доступную память.

2 голосов
/ 01 ноября 2011

Я думаю, что теперь у него было столько ответов, сколько он получит.Вот краткое изложение:

i) никто не предложил какой-либо фундаментальной причины ограничения стека ниже, чем общий объем доступной памяти

ii) ответ, который я узнал больше всего, былБрайана (большое спасибо).Я настоятельно рекомендую пост в блоге, на который он ссылался, и остальную часть его блога.Я нашел это более информативным, чем любая из двух хороших книг по F #, которые у меня есть.(Сказав это, вы, вероятно, должны взглянуть на то, что он говорит о том, насколько просто достичь хвостовой рекурсии в части 6 сообщений в блоге о катаморфизмах https://lorgonblog.wordpress.com/2008/06/02/catamorphisms-part-six/, прежде чем вы произнесете слово «маргинальный», которое он имелиспользуется по номинальной стоимости :-)).

РЕДАКТИРОВАТЬ: Джон Харроп был очень близкий второй.Большое спасибо.

iii) Свик предложил простой способ увеличения лимита на три порядка.Большое спасибо.

iv) Делнан предположил, что лучшая практика заключается в том, чтобы просто использовать фолды / карты везде и определять их хвост-рекурсивным способом.Это, конечно, хороший совет для списков, но, возможно, менее применимый при обходе графиков.В любом случае, большое спасибо за предложение.

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

1 голос
/ 01 ноября 2011

В большинстве случаев стек не будет проблемой, если вы напишите свои функции как хвостовую рекурсивную

...