performance - ¿Haskell es apropiado para aplicaciones de larga duración?
memory-leaks functional-programming (5)
El servidor web warp demuestra que Haskell es apropiado para aplicaciones de larga ejecución.
Cuando las aplicaciones de Haskell tienen pérdidas de espacio, puede ser difícil rastrear la causa, pero una vez que se conoce la causa, generalmente es trivial de resolver (la solución más difícil que he tenido que usar es aplicar zip [1..]
a un enumere y obtenga la longitud del último elemento en lugar de usar la función de length
). Pero las fugas de espacio son realmente muy raras en los programas de Haskell. Por lo general, es más difícil crear deliberadamente una fuga de espacio que corregir una accidental.
Creo que Haskell es un lenguaje hermoso, y a juzgar por los puntos de referencia, sus implementaciones pueden generar código rápido.
Sin embargo, me pregunto si es apropiado para aplicaciones de larga ejecución, o si perseguir todas las posibles fugas inducidas por la pereza, que uno podría ignorar en una aplicación de corta duración, ¿sería frustrante?
Este comentario de Reddit echos mis preocupaciones:
Tan pronto como tiene más de una función que se llama a sí misma de forma recursiva, el perfil de pila deja de brindarle ayuda para localizar dónde se está produciendo la fuga.
(Toda la discusión parece perspicaz y franca)
Personalmente estoy interesado en la computación de alto rendimiento, pero creo que los servidores y HPC tienen este requisito en común.
Si Haskell es apropiado para tales aplicaciones, ¿hay ejemplos que prueben este punto, es decir, aplicaciones que
- debe ejecutarse durante días o semanas, por lo tanto, se requiere la eliminación de todas las fugas relevantes (el tiempo que el programa pasa durmiendo o esperando que regrese una biblioteca de C subyacente obviamente no cuenta)
- no son triviales (si la aplicación es simple, el desarrollador podría simplemente adivinar el origen de la fuga e intentar varias correcciones. Sin embargo, no creo que este enfoque se adapte bien. La utilidad del perfil del montón para identificar el origen del La (s) fuga (s) con múltiples funciones recursivas [mutuamente] parece ser de particular preocupación, según el análisis de Reddit anterior)
Si Haskell no es apropiado para tales aplicaciones, ¿por qué?
Actualización: el marco del servidor web Yesod para Haskell, que se presentó como ejemplo, puede tener problemas con la memoria . Me pregunto si alguien probó su uso de memoria después de atender solicitudes continuamente durante días.
Es. Hay 2 tipos de posibles fugas de espacio:
Datos sobre el montón. Aquí la situación no es diferente de otros idiomas que usan GC. (Y para los que no lo hacen, la situación suele ser peor: si hay un error, en lugar de aumentar el uso de la memoria, el proceso podría tocar la memoria liberada o viceversa y simplemente fallar).
Thunks sin evaluar. Es cierto que uno puede dispararse en el pie; por supuesto, debe evitar las situaciones bien conocidas que producen grandes troncos como foldl (+) 0
. Pero no es difícil evitar eso, y para otras filtraciones diría que en realidad es más fácil que en otros idiomas, cuando te acostumbras.
Ya sea que tenga una computación pesada de larga duración o un servicio que responda a las solicitudes. Si tiene un cálculo de larga ejecución, por lo general necesita resultados inmediatamente a medida que los calcula, lo que obliga a su evaluación.
Y si tiene un servicio, su estado generalmente está bien contenido, por lo que es fácil asegurarse de que siempre se evalúe al final de una solicitud. De hecho, Haskell lo hace más fácil en comparación con otros idiomas: en Haskell, no es posible que los componentes de su programa mantengan su propio estado interno. El estado global de la aplicación se enlaza como argumentos en algún tipo de bucle principal o se almacena mediante IO
. Y dado que un buen diseño de una aplicación de Haskell limita y localiza la IO
tanto como sea posible, de nuevo hace que el estado sea fácil de controlar.
Como otro ejemplo, el proyecto Ganeti (del cual soy un desarrollador) usa varios demonios de larga ejecución de Haskell.
Desde nuestra experiencia, las fugas de memoria han sido muy raras, si tuvimos problemas, generalmente se realizó con otros recursos (como descriptores de archivos). El único caso un tanto reciente que recuerdo fue el daemon de monitoreo que pierde la memoria como thunks en el raro caso en que recolectó datos, pero nadie los miró (lo que forzaría su evaluación). La solución era bastante simple .
La mayoría de las aplicaciones de larga duración son impulsadas por solicitudes. Por ejemplo, los servidores HTTP asocian todos los datos transitorios con una solicitud HTTP. Una vez finalizada la solicitud, los datos se desechan. Por lo tanto, al menos para ese tipo de aplicaciones de larga duración, cualquier idioma no tendrá pérdidas de espacio. Fuga todo lo que quieras en el contexto de una sola solicitud. Mientras no cree referencias globales a los datos por solicitud, no se filtrará.
Todas las apuestas están desactivadas si muta el estado global. Esto se debe evitar por muchas razones y es poco común en tales aplicaciones.
Las "fugas de espacio" son semánticamente idénticas a cualquier otro tipo de problema de uso de recursos en cualquier idioma. En idiomas estrictos, el GC tiende a asignar y retener demasiados datos (ya que las estructuras son estrictas).
No importa el idioma en el que deba estar "grabándose" para buscar el uso de recursos a lo largo del tiempo, y Haskell no es diferente.
Véase, por ejemplo, xmonad
, que se ejecuta durante meses o años a la vez. Es una aplicación de Haskell, tiene un pequeño uso de pila, y la probé durante semanas o meses con el perfil para analizar los patrones de pila. Esto me da confianza de que el uso de recursos es estable.
En última instancia, sin embargo, la pereza es una pista falsa aquí. Use las herramientas de monitoreo de recursos y las pruebas para medir y validar sus expectativas de recursos.
Tengo un servicio escrito en haskell que funciona durante meses sin ningún problema específico de haskell. Hubo un período en el que funcionó durante 6 meses sin ninguna atención, pero luego lo reinicié para actualizarlo. Contiene una API HTTP sin estado, pero también tiene una interfaz de websockets con estado, por lo que mantiene un estado de larga vida. Sus fuentes están cerradas, por lo que no puedo proporcionar un enlace, pero mi experiencia haskell funciona bien para aplicaciones de larga ejecución.
La pereza no es un problema para mí, pero eso es porque sé cómo lidiar con eso. No es difícil, pero requiere algo de experiencia.
También las bibliotecas en hackage tienen una calidad diferente, y mantener las dependencias bajo control es algo importante. Intento evitar las dependencias a menos que sean realmente necesarias, e inspecciono la mayor parte de su código (excepto en algunos paquetes muy utilizados, la mayoría de ellos son bibliotecas centrales o parte de la Plataforma Haskell, aunque también inspecciono su código, solo para aprender cosas nuevas.)
Aunque hay casos de esquina cuando GHC (la implementación más utilizada) no funciona lo suficientemente bien. Tuve problemas con el tiempo de GC cuando una aplicación mantiene un gran estado (casi solo de lectura) en la memoria (hay un ticket ). También muchos punteros estables pueden ser problemáticos (el ticket , aunque yo nunca lo experimenté). La mayoría de los El tiempo como los casos de esquina son fáciles de evitar por un diseño cuidadoso.
En realidad, el diseño de aplicaciones es lo más importante para las aplicaciones de larga ejecución. El lenguaje de implementación juega un papel menos importante. Probablemente es la lección más importante que aprendí en los últimos años: el diseño de software es muy importante y no es muy diferente entre los idiomas.