В первом и последнем примерах исходная последовательность, прошедшая до split-with
, сохраняется при полной реализации в памяти; следовательно, OOME. То, как это происходит, является косвенным; то, что удерживается непосредственно, составляет t
, а исходная последовательность удерживается t
, ленивая секунда, в ее нереализованной состоянии.
Путь t
приводит к тому, что первоначальная последовательность должна быть проведена следующим образом. До реализации, t
является объектом LazySeq
, хранящим thunk, который может быть вызван в какой-то момент для реализации t
; этому thunk необходимо сохранить указатель на исходный аргумент последовательности до split-with
, прежде чем он будет реализован, чтобы передать его на take-while
- см. реализацию split-with
. Как только t
реализуется, бит имеет право на GC (поле, которое удерживает его в объекте LazySeq
, установлено на null
) на t
больше не удерживает голову огромного ввода.
Входной seq сам реализуется полностью (count d)
, который должен реализовать d
, и, таким образом, исходный входной сигнал.
Переходя почему t
в настоящее время сохраняется:
В первом случае, это происходит потому, что (count d)
получает оценку до того (count t)
. Поскольку Clojure оценивает эти выражения слева направо, локальный t
должен зависнуть для второго вызова для подсчета, и поскольку он, как правило, удерживается на огромном seq (как объяснялось выше), это приводит к OOME.
Окончательный пример, когда возвращается только (count d)
, должен в идеале не удержаться до t
; причина, по которой это не так, несколько тонкая и лучше всего объясняется ссылкой на второй пример.
Второй пример работает нормально, потому что после оценки (count t)
t
больше не требуется. Компилятор Clojure замечает это и использует умный трюк, чтобы локальный сброс до nil
одновременно с созданным вызовом count
. Критическая часть кода Java делает что-то вроде f(t, t=null)
, так что текущее значение t
передается соответствующей функции, но локаль очищается до того, как управление передано в f
, так как это происходит как побочный эффект выражения t=null
, который является аргументом f
; очевидно, что семантика Java слева направо - ключ к этой работе.
Обратно к окончательному примеру это не работает, потому что t
фактически не используется в любом месте, а неиспользуемые местные жители не обрабатываются процессом очистки местных жителей. (Очистка происходит в точке последнего использования, при отсутствии такой точки в программе нет клиринга.)
Что касается count
, осуществляющих ленивые последовательности: он должен это делать, поскольку нет общего способа предсказывая длину ленивого seq, не осознавая этого.
Вы говорите, что «местный т необходимо повесить на второй вызов, чтобы подсчитать, а так как это происходит, чтобы указать на огромный seq, что приводит к OOME». Но 't' составляет всего 12 пунктов. Как избавиться от 't' играть такую огромную роль, если она составляет 12 элементов. И сохранение' d' в памяти не является проблемой, хотя это все остальное '(диапазон 1e8)' –
Ну, 't 'до того, как он используется каким-то образом, - это нереализованный ленивый seq, который внутренне содержит некоторый код, который в какой-то момент может быть вызван для реализации' t'. Этот код должен содержать указатель на исходную последовательность, переданную в 'split-with', поэтому он может передать ее на' take-while' (см. Реализацию 'split-with'). Спасибо за комментарий - это должно быть частью ответа, я отредактирую его. –
Итак, что вы говорите, это то, что 't' удерживается, он фактически содержит целую исходную последовательность в памяти? Итак, если вычисляется '(count d)', параллельно с которым мы имеем clojure, сохраняя всю исходную последовательность, реализованную в памяти? –