functional-programming clojure lazy-evaluation jvm-languages

functional programming - Explicación de "Pierde la cabeza" en secuencias perezosas



functional-programming clojure (4)

Lo que realmente mantiene la cabeza aquí es la unión de la secuencia a r (no la ya evaluada (first r) , ya que no se puede evaluar la secuencia completa a partir de su valor).

En el primer caso, el enlace ya no existe cuando se evalúa (last r) , ya que no hay más expresiones con r para evaluar. En el segundo caso, la existencia de un no evaluado aún (first r) significa que el evaluador debe mantener la vinculación con r .

Para mostrar la diferencia, esto se evalúa OK:

user> (let [r (range 1e8) a 7] [(last r) ((constantly 5) a)]) [99999999 5]

Si bien esto falla:

(let [r (range 1e8) a 7] [(last r) ((constantly 5) r)])

Aunque la siguiente expresión (last r) ignora r , el evaluador no es tan inteligente y mantiene la vinculación con r , manteniendo así toda la secuencia.

Edit: he encontrado un post donde Rich Hickey explica los detalles del mecanismo responsable de borrar la referencia a la cabeza en los casos anteriores. Aquí está: Rich Hickey en la limpieza de locales

En el lenguaje de programación Clojure, ¿por qué este código pasa con gran éxito?

(let [r (range 1e9)] [(first r) (last r)])

Si bien éste falla:

(let [r (range 1e9)] [(last r) (first r)])

Sé que se trata del consejo de "Perder la cabeza" pero, ¿podría explicármelo, por favor? Todavía no soy capaz de digerirlo.

ACTUALIZAR:
Es realmente difícil elegir la respuesta correcta, dos respuestas son asombrosamente informativas.
Nota: los fragmentos de código son de "The Joy of Clojure".


Para elaborar las dfan de dfan y Rafał , me tomé el tiempo de ejecutar ambas expresiones con el perfilador de YourKit .

Es fascinante ver a la JVM en acción. El primer programa es tan amigable con GC que la JVM realmente brilla en la gestión de su memoria.

Dibujé algunos cuadros.

Compatible con GC: (sea [r (rango 1e9)] [(primera r) (última r)])

Este programa se ejecuta muy bajo en memoria; En general, menos de 6 megabytes. Como se dijo anteriormente, es muy amigable con el GC, hace muchas colecciones, pero utiliza muy poca CPU para eso.

Soporte para la cabeza: (deje [r (rango 1e9)] [(última r) (primera r)])

Esta tiene mucha memoria hambrienta. Sube a 300 MB de RAM, pero eso no es suficiente y el programa no finaliza (la JVM muere menos de un minuto después). El GC toma hasta el 90% del tiempo de CPU, lo que indica que intenta desesperadamente liberar cualquier memoria que pueda, pero no puede encontrar ninguna (los objetos recopilados son muy poco o nada).

Editar El segundo programa se quedó sin memoria, lo que provocó un volcado de pila. Un análisis de este volcado muestra que el 70% de la memoria son objetos java.lang.Integer, que no se pudieron recopilar. Aquí hay otra captura de pantalla:


para una descripción técnica, vaya a http://clojure.org/lazy . El consejo se menciona en la sección Don''t hang (onto) your head


range genera elementos según sea necesario.

En el caso de (let [r (range 1e9)] [(first r) (last r)]) , toma el primer elemento (0), luego genera un billón - 2 elementos, arrojándolos a medida que avanza, y luego agarra el último elemento (999,999,999). Nunca tiene ninguna necesidad de mantener parte de la secuencia.

En el caso de (let [r (range 1e9)] [(last r) (first r)]) , genera un billón de elementos para poder evaluar (last r) , pero también tiene que aferrarse a El principio de la lista se está generando para poder evaluar más tarde (first r) . Así que no puede tirar nada a medida que avanza, y (supongo) se queda sin memoria.