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.