Почему F # накладывает ограничение на размер стека?
Я хотел бы знать, существует ли фундаментальная причина ограничения глубины рекурсии в F # до 10000 или около того, и в идеале, как избежать этого предела. Я думаю, что вполне разумно писать код, который использует пространство стека O (n), и был бы признателен, если кто-то, кто не согласен, может объяснить, почему они это делают. Большое спасибо. Я объясняю свое мышление ниже.
Я не вижу, что есть какая-то причина не позволять стеку расти до тех пор, пока вся доступная память не будет исчерпана. Это означало бы, что бесконечная рекурсия займет больше времени, но это не так, как будто мы не можем писать программы, которые потребляют бесконечное количество памяти. Я знаю, что можно сократить использование стека до O (1) с использованием продолжений и хвостовой рекурсии, но я не особо вижу, как мне хорошо делать это все время. Я также не вижу, как это помогает знать, когда функция, вероятно, потребуется обработать "большой" вход (ну, по стандарту 8-битного микроконтроллера).
Я думаю, что это принципиально отличается от того, чтобы, например, используйте накопительные параметры, чтобы избежать квадратичного поведения времени. Хотя это также вызывает беспокойство по поводу деталей реализации и не нужно делать для "малых" входов, это также очень отличается от того, что компилятор не может тривиально удалить проблему самостоятельно. Кроме того, он отличается тем, что немного сложный O (n) код, который был бы O (n ^ 2), если он наивно очень полезен, чем простая, медленная, легко читаемая версия. Напротив, код стиля продолжения имеет точно такую же сложность памяти, что и соответствующая наивная версия, но просто использует различный вид памяти. Это то, о чем компилятор не должен беспокоить меня в этот день и возраст?
В то время как я бы предпочел теоретическую причину того, почему невозможно иметь глубокий стек, мы могли бы также обсудить практические аспекты. Мне кажется, что стек представляет собой несколько более эффективный способ управления памятью, чем куча, поскольку он не требует сбора мусора и легко освобождается? Я не уверен, что могу даже увидеть, что есть затраты на создание глубоких стеков. По общему признанию, ОС необходимо выделить достаточно виртуального пространства, чтобы содержать всю память, которую вы могли бы использовать сразу во всей программе для каждого стека потоков. Но так что. Это не так, как если бы мы, скорее всего, исчерпали нынешний общий 48-битный лимит, или производители оборудования не могут тривиально увеличить этот предел до 64 бит?
Здесь нет особого значения для F #. Я ожидаю, что такое же ограничение применимо и к С#, и не увидит, что он там больше необходим, хотя на практике при программировании в императивном стиле явно намного меньше.
Большое спасибо за любые ответы/комментарии.
EDIT: я написал краткое изложение ответов ниже.
Ответы
Ответ 1
В теории все возможно. Вы можете написать компилятор, который использует кучу для управления традиционным "стек".
На практике важна производительность (особенно для таких фундаментальных функций, как "вызовы функций" ). У нас есть полвека аппаратных средств и операционной системы, адаптированных/оптимизированных для модели конечной модели памяти.
Это то, о чем компилятор не должен беспокоить меня в этот день и возраст?
Мех. Сбор мусора - большая победа; Управление всей вашей памятью - это, в основном, ненужная работа, и многие приложения могут комбинировать некоторые характеристики производительности для программистов здесь. Но я думаю, что немногие люди считают, что человеческое управление стеком/рекурсией - это огромная сделка, даже в функциональном языке, поэтому значение, позволяющее программисту отключиться отсюда, - это ИМО, маргинальный.
Обратите внимание, что в F # специально вы можете использовать рабочий процесс CPS, который преобразует справедливый бит стека в кучи и/или хвостовые вызовы с относительно небольшими изменениями в стиле/синтаксисе программирования, если вы хотите туда попасть ( см., например, здесь).
Ответ 2
На сегодняшний день наиболее убедительной причиной того, что F # наследует ограничения .NET в этом контексте, является совместимость. Компиляторы могут и полностью исключают стек, например. SML/NJ компилятор для стандартного ML трансформирует программы в продолжение стиля передачи автоматически. Два основных недостатка в том, что для этого требуется глобальное изменение соглашения о вызове, которое нарушает совместимость и что оно существенно менее эффективно. Если F # сделал это, чтобы избежать, то совместимость с С# была бы намного сложнее, а F # было бы намного медленнее.
Еще одна причина, по которой глубокие стеки - плохая идея, - сборщик мусора. Стеки обрабатываются специально GC, потому что они гарантированно являются нитями локальными и могут сокращаться без необходимости сбора..NET GC обходит все стеки потоков всякий раз, когда какой-либо поток несет коллекцию gen0. Следовательно, наличие только двух спальных нитей с глубокими стеками может привести к тому, что другой поток будет работать на 10x медленнее. Представьте, насколько хуже было бы с гораздо более глубокими стеками. Это можно решить, изменив способ, которым GC обрабатывает стеки, по существу превращая их в кучи, но это делает операции стека намного медленнее.
Ответ 3
Вы можете легко избежать этого ограничения: просто запустите новый поток, у которого есть стек настолько большим, насколько вы хотите, используя перегрузку конструктора для Thread
.
Ответ 4
Я могу думать по крайней мере о двух возможных причинах:
(1) На многих компьютерных архитектурах трудно увеличить размер, доступный для стека во время выполнения, не перемещая его в другом месте в адресном пространстве. Как отметил @svick в комментариях,.NET на 32-битной Windows ограничивает размер стека основного потока до 1 МБ. Изменение размера стека основного потока в Windows требует изменения файла .EXE.
(2) FAR, FAR более распространен для, вызванного ошибкой программирования, чем при наличии кода, который действительно должен превышать размер доступного стека. Ограниченный размер стека - очень полезный дозор, чтобы уловить ошибки программирования. В 98% случаев, если стеку было позволено расти в свободную память, в первый раз, когда вы узнаете о своих ошибках программирования, вы будете исчерпаны доступная память.
Ответ 5
Я думаю, что у этого теперь было столько ответов, сколько он собирается получить. Вот резюме:
i) никто не предложил какую-либо фундаментальную причину ограничения стека ниже общего объема доступной памяти
ii) ответ, который я узнал больше всего, был Брайан (большое спасибо). Я бы настоятельно рекомендовал сообщение в блоге, которое он связал, и остальную часть его блога. Я нашел его более информативным, чем любая из двух хороших книг на F # I. (Сказав это, вам, вероятно, следует взглянуть на то, что он говорит о том, насколько просто добиться конечной рекурсии в части 6 сообщений в блогах о катаморфизмах https://lorgonblog.wordpress.com/2008/06/02/catamorphisms-part-six/, прежде чем вы примете слово "маргинальный", который он использовал по номиналу:-)).
EDIT: Джон Харроп был очень близким вторым. Большое спасибо.
iii) Свик предложил простой способ увеличить предел на три порядка. Большое спасибо.
iv) Дельнан предположил, что наилучшей практикой является просто использовать карты/карты во всем мире и определять их хвостовым рекурсивным способом. Это, безусловно, разумный совет для списков, но, возможно, менее применим при пересечении графиков. В любом случае, большое спасибо за предложение.
v) Джоэл и Брайан предложили некоторые практические причины того, почему предел является хорошей идеей. Все они были низкоуровневыми деталями, которые, как я чувствую, должны быть хорошо скрыты языком высокого уровня. Большое спасибо.
Ответ 6
В большинстве случаев стека не будет проблемой, если вы напишете свои функции как хвост рекурсивный