Estoy de acuerdo con Dietrich Epp: es una combinación de varias cosas que hacen que GHC sea rápido.
Ante todo, Haskell es de muy alto nivel. Esto permite que el compilador realice optimizaciones agresivas sin romper su código.
Piensa en SQL. Ahora, cuando escribo una SELECT
declaración, puede parecer un bucle imperativo, pero no lo es . Puede parecer que recorre todas las filas de esa tabla tratando de encontrar la que coincida con las condiciones especificadas, pero en realidad el "compilador" (el motor DB) podría estar haciendo una búsqueda de índice, que tiene características de rendimiento completamente diferentes. Pero debido a que SQL es de tan alto nivel, el "compilador" puede sustituir algoritmos totalmente diferentes, aplicar múltiples procesadores o canales de E / S o servidores completos de forma transparente y más.
Pienso en Haskell como el mismo. Puede pensar que acaba de pedirle a Haskell que asigne la lista de entrada a una segunda lista, filtre la segunda lista en una tercera lista y luego cuente cuántos elementos se obtuvieron. Pero no vio que GHC aplicaba reglas de reescritura de fusión de flujo detrás de escena, transformando todo en un solo bucle de código de máquina ajustado que hace todo el trabajo en un solo paso sobre los datos sin asignación, el tipo de cosa que ser tedioso, propenso a errores y no mantenible para escribir a mano. Eso solo es realmente posible debido a la falta de detalles de bajo nivel en el código.
Otra forma de verlo podría ser ... ¿por qué Haskell no debería ser rápido? ¿Qué hace que lo haga lento?
No es un lenguaje interpretado como Perl o JavaScript. Ni siquiera es un sistema de máquina virtual como Java o C #. Se compila hasta el código de máquina nativo, por lo que no hay gastos generales allí.
A diferencia de los lenguajes OO [Java, C #, JavaScript ...], Haskell tiene borrado de tipo completo [como C, C ++, Pascal ...]. Toda verificación de tipo ocurre solo en tiempo de compilación. Por lo tanto, tampoco hay una verificación de tipo en tiempo de ejecución para ralentizarlo. (No hay comprobaciones de puntero nulo, para el caso. En, digamos, Java, la JVM debe verificar los punteros nulos y lanzar una excepción si hace una deferencia. Haskell no tiene que molestarse con ese cheque).
Dices que suena lento para "crear funciones sobre la marcha en tiempo de ejecución", pero si miras con mucho cuidado, en realidad no lo haces. Puede parecer que lo haces, pero no lo haces. Si dices (+5)
, bueno, eso está codificado en tu código fuente. No puede cambiar en tiempo de ejecución. Entonces no es realmente una función dinámica. Incluso las funciones currificadas en realidad solo guardan parámetros en un bloque de datos. Todo el código ejecutable existe realmente en tiempo de compilación; No hay interpretación en tiempo de ejecución. (A diferencia de otros idiomas que tienen una "función de evaluación").
Piensa en Pascal. Es viejo y ya nadie lo usa, pero nadie se quejaría de que Pascal es lento . Hay muchas cosas que no le gustan, pero la lentitud no es realmente una de ellas. Haskell realmente no está haciendo tanto que sea diferente a Pascal, aparte de tener recolección de basura en lugar de administración de memoria manual. Y los datos inmutables permiten varias optimizaciones para el motor GC [cuya evaluación diferida complica un poco].
Creo que la cuestión es que Haskell se ve avanzado, sofisticado y de alto nivel, y todos piensan "¡oh wow, esto es realmente poderoso, debe ser increíblemente lento! " Pero no lo es. O al menos, no está en la forma que esperarías. Sí, tiene un sistema de tipos increíble. ¿Pero sabes que? Todo eso sucede en tiempo de compilación. En tiempo de ejecución, se ha ido. Sí, le permite construir ADT complicados con una línea de código. ¿Pero sabes que? Un ADT es simplemente una C ordinaria union
de struct
s. Nada mas.
El verdadero asesino es la evaluación perezosa. Cuando obtienes la rigidez / pereza de tu código correctamente, puedes escribir código estúpidamente rápido que todavía es elegante y hermoso. Pero si te equivocas con esto, tu programa va miles de veces más lento , y realmente no es obvio por qué sucede esto.
Por ejemplo, escribí un pequeño programa trivial para contar cuántas veces aparece cada byte en un archivo. Para un archivo de entrada de 25 KB, el programa tardó 20 minutos en ejecutarse y se tragó 6 gigabytes de RAM. ¡¡Eso es absurdo!! Pero luego me di cuenta de cuál era el problema, agregué un solo patrón de explosión y el tiempo de ejecución se redujo a 0.02 segundos .
Aquí es donde Haskell va inesperadamente lento. Y seguro que lleva un tiempo acostumbrarse. Pero con el tiempo, se vuelve más fácil escribir código realmente rápido.
¿Qué hace que Haskell sea tan rápido? Pureza. Tipos estáticos Pereza. Pero, sobre todo, tener un nivel suficientemente alto para que el compilador pueda cambiar radicalmente la implementación sin romper las expectativas de su código.
Pero supongo que esa es solo mi opinión ...