Объяснение "Потеряйте голову" в ленивых последовательностях
В Clojure языке программирования, почему этот код проходит с летающими цветами?
(let [r (range 1e9)] [(first r) (last r)])
Пока это не удается:
(let [r (range 1e9)] [(last r) (first r)])
Я знаю, что речь идет о совете "Потеря головы", но не могли бы вы объяснить мне это? Я еще не могу его переварить.
UPDATE:
На самом деле трудно выбрать правильный ответ, два ответа удивительно информативны.
Примечание. Отрывки кода взяты из "Радости Clojure".
Ответы
Ответ 1
range
генерирует элементы по мере необходимости.
В случае (let [r (range 1e9)] [(first r) (last r)])
он захватывает первый элемент (0), затем генерирует миллиард-2 элемента, выкидывая их по мере их поступления, а затем захватывает последний элемент (999,999,999). Ему никогда не нужно сохранять какую-либо часть последовательности.
В случае (let [r (range 1e9)] [(last r) (first r)])
он генерирует миллиард элементов, чтобы иметь возможность оценить (last r)
, но он также должен удержаться в начале создаваемого им списка, чтобы позже оценить (first r)
, Таким образом, он не может ничего выбросить, поскольку он идет, и (я полагаю) исчерпывает память.
Ответ 2
Чтобы уточнить ответы dfan и Rafał, я потратил время на выполнение обоих выражений с помощью YourKit профилировщик.
Увлекательно видеть JVM на работе. Первая программа настолько GC-friendly, что JVM действительно сияет при управлении своей памятью.
Я нарисовал несколько диаграмм.
GC friendly: (пусть [r (диапазон 1e9)] [(первый r) (последний r)])
![enter image description here]()
Эта программа работает очень низко в памяти; в целом, менее 6 мегабайт. Как уже говорилось ранее, он очень дружелюбен к GC, он делает множество коллекций, но для этого использует очень небольшой процессор.
Держатель головки: (пусть [r (диапазон 1e9)] [(последний r) (первый r)])
![enter image description here]()
Это очень голодная память. Он доходит до 300 МБ ОЗУ, но этого недостаточно, и программа не заканчивается (JVM умирает менее чем через минуту). GC занимает до 90% времени процессора, что указывает на то, что он отчаянно пытается освободить любую память, которую он может, но не может найти (собранные объекты очень мало).
Изменить Вторая программа закончилась без памяти, что вызвало сброс кучи. Анализ этого дампа показывает, что 70% памяти - это объекты java.lang.Integer, которые невозможно было собрать. Вот еще один снимок экрана:
![enter image description here]()
Ответ 3
Что действительно держит голову здесь, так это привязка последовательности к r
(а не уже оцененная (first r)
, так как вы не можете оценить всю последовательность из ее значения.)
В первом случае привязка больше не существует, когда (last r)
оценивается, так как для выражения не существует больше выражений с r
. Во втором случае существование еще не оцененного (first r)
означает, что оценщику необходимо сохранить привязку к r
.
Чтобы показать разницу, это оценивает OK:
user> (let [r (range 1e8) a 7] [(last r) ((constantly 5) a)])
[99999999 5]
Пока это не удается:
(let [r (range 1e8) a 7] [(last r) ((constantly 5) r)])
Несмотря на то, что выражение, следующее за (last r)
, игнорирует r
, оценщик не является таким умным и сохраняет привязку к r
, сохраняя при этом всю последовательность.
Изменить: Я нашел сообщение, в котором Rich Hickey объясняет детали механизма, ответственного за очистку ссылки на голову в вышеупомянутых случаях. Вот он: Rich Hickey на расчистке локальных жителей
Ответ 4
для технического описания, перейдите в http://clojure.org/lazy. совет указан в разделе Don't hang (onto) your head