Acabo de aprender cómo funciona la evaluación diferida y me preguntaba: ¿por qué no se aplica la evaluación diferida en cada software que se produce actualmente? ¿Por qué sigue usando una evaluación entusiasta?
lazy-initialization
John Smith
fuente
fuente
Respuestas:
La evaluación perezosa requiere gastos generales de contabilidad; debe saber si ya se ha evaluado y esas cosas. La evaluación ansiosa siempre se evalúa, por lo que no tiene que saberlo. Esto es especialmente cierto en contextos concurrentes.
En segundo lugar, es trivial convertir una evaluación entusiasta en una evaluación perezosa empaquetándola en un objeto de función que se llamará más adelante, si así lo desea.
En tercer lugar, la evaluación perezosa implica una pérdida de control. ¿Qué pasa si evalúo perezosamente la lectura de un archivo de un disco? ¿O tomando el tiempo? Eso no es aceptable.
La evaluación entusiasta puede ser más eficiente y más controlable, y se convierte trivialmente en evaluación perezosa. ¿Por qué querrías una evaluación perezosa?
fuente
readFile
es exactamente lo que necesito. Además, la conversión de la evaluación perezosa a la ansiosa es igual de trivial.head [1 ..]
te da en un lenguaje puro evaluado con entusiasmo, porque en Haskell sí1
?Principalmente porque el código diferido y el estado pueden mezclarse mal y causar algunos errores difíciles de encontrar. Si el estado de un objeto dependiente cambia, el valor de su objeto vago puede ser incorrecto cuando se evalúa. Es mucho mejor que el programador codifique explícitamente el objeto para que sea perezoso cuando sepa que la situación es apropiada.
En una nota al margen, Haskell usa la evaluación Lazy para todo. Esto es posible porque es un lenguaje funcional y no usa estado (excepto en algunas circunstancias excepcionales donde están claramente marcados)
fuente
set!
en un intérprete de Scheme flojo. > :(La evaluación perezosa no siempre es mejor.
Los beneficios de rendimiento de la evaluación diferida pueden ser excelentes, pero no es difícil evitar la mayoría de las evaluaciones innecesarias en entornos ansiosos; seguramente la pereza lo hace fácil y completo, pero rara vez la evaluación innecesaria en el código es un problema importante.
Lo bueno de la evaluación diferida es cuando te permite escribir código más claro; obtener la décima prima filtrando una lista infinita de números naturales y tomando el décimo elemento de esa lista es una de las formas más concisas y claras de proceder: (pseudocódigo)
Creo que sería bastante difícil expresar las cosas de manera tan concisa sin pereza.
Pero la pereza no es la respuesta a todo. Para empezar, la pereza no se puede aplicar de forma transparente en presencia de estado, y creo que la apatía no se puede detectar automáticamente (a menos que esté trabajando, por ejemplo, Haskell, cuando el estado es bastante explícito). Por lo tanto, en la mayoría de los idiomas, la pereza debe hacerse manualmente, lo que hace que las cosas sean menos claras y, por lo tanto, elimina uno de los grandes beneficios de la evaluación diferida.
Además, la pereza tiene inconvenientes de rendimiento, ya que incurre en una sobrecarga significativa de mantener las expresiones no evaluadas; usan el almacenamiento y son más lentos para trabajar que los valores simples. No es raro descubrir que debe codificar con entusiasmo porque la versión perezosa es lenta y, a veces, es difícil razonar sobre el rendimiento.
Como suele suceder, no existe una mejor estrategia absoluta. Lazy es excelente si puede escribir un mejor código aprovechando las estructuras de datos infinitas u otras estrategias que le permite usar, pero ansioso puede ser más fácil de optimizar.
fuente
Aquí hay una breve comparación de los pros y los contras de una evaluación ansiosa y perezosa:
Evaluación ansiosa:
Sobrecarga potencial de evaluar cosas innecesariamente.
Sin trabas, evaluación rápida.
Evaluación perezosa:
No hay evaluación innecesaria.
Gastos generales de contabilidad en cada uso de un valor.
Entonces, si tiene muchas expresiones que nunca tienen que ser evaluadas, lazy es mejor; sin embargo, si nunca tiene una expresión que no necesita ser evaluada, lazy es pura sobrecarga.
Ahora, echemos un vistazo al software del mundo real: ¿cuántas de las funciones que escribe no requieren la evaluación de todos sus argumentos? Especialmente con las funciones cortas modernas que solo hacen una cosa, el porcentaje de funciones que entran en esta categoría es muy bajo. Por lo tanto, la evaluación perezosa solo introduciría la sobrecarga de contabilidad la mayor parte del tiempo, sin la posibilidad de guardar realmente nada.
En consecuencia, la evaluación perezosa simplemente no paga en promedio, la evaluación entusiasta es la mejor opción para el código moderno.
fuente
Como señaló @DeadMG, la evaluación diferida requiere gastos generales de contabilidad. Esto puede ser costoso en relación con la evaluación entusiasta. Considere esta afirmación:
Esto tomará un poco de cálculo para calcular. Si uso una evaluación diferida, entonces debo verificar si se ha evaluado cada vez que la uso. Si esto está dentro de un circuito cerrado muy utilizado, la sobrecarga aumenta significativamente, pero no hay beneficio.
Con una evaluación entusiasta y un compilador decente, la fórmula se calcula en tiempo de compilación. La mayoría de los optimizadores moverán la asignación de cualquier bucle en el que ocurra, si corresponde.
La evaluación diferida es la más adecuada para cargar datos a los que se accede con poca frecuencia y tiene una alta sobrecarga para recuperar. Por lo tanto, es más apropiado para casos extremos que la funcionalidad principal.
En general, es una buena práctica evaluar cosas a las que se accede con frecuencia lo antes posible. La evaluación perezosa no funciona con esta práctica. Si siempre tendrá acceso a algo, lo único que hará una evaluación perezosa es agregar gastos generales. El costo / beneficio del uso de la evaluación diferida disminuye a medida que es menos probable que se acceda al elemento al que se accede.
Usar siempre una evaluación diferida también implica una optimización temprana. Esta es una mala práctica que a menudo da como resultado un código que es mucho más complejo y costoso que de lo contrario podría ser el caso. Desafortunadamente, la optimización prematura a menudo da como resultado un código que funciona más lentamente que un código más simple. Hasta que pueda medir el efecto de la optimización, es una mala idea optimizar su código.
Evitar la optimización prematura no entra en conflicto con las buenas prácticas de codificación. Si no se aplicaron las buenas prácticas, las optimizaciones iniciales pueden consistir en aplicar buenas prácticas de codificación, como mover cálculos fuera de los bucles.
fuente
Si potencialmente tenemos que evaluar completamente una expresión para determinar su valor, la evaluación diferida puede ser una desventaja. Digamos que tenemos una larga lista de valores booleanos y queremos saber si todos son verdaderos:
Para hacer esto, tenemos que mirar cada elemento de la lista, sin importar qué, así que no hay posibilidad de cortar perezosamente la evaluación. Podemos usar un pliegue para determinar si todos los valores booleanos en la lista son verdaderos. Si usamos un pliegue a la derecha, que usa la evaluación diferida, no obtenemos ninguno de los beneficios de la evaluación diferida porque tenemos que mirar cada elemento de la lista:
Un pliegue a la derecha será mucho más lento en este caso que un pliegue estricto a la izquierda, que no utiliza una evaluación perezosa:
La razón es que un pliegue estricto hacia la izquierda utiliza la recursividad de cola, lo que significa que acumula el valor de retorno y no se acumula y almacena en la memoria una gran cadena de operaciones. Esto es mucho más rápido que el plegado lento a la derecha porque ambas funciones tienen que mirar la lista completa de todos modos y el plegado a la derecha no puede usar la recursión de cola. Entonces, el punto es que debes usar lo que sea mejor para la tarea en cuestión.
fuente