La programación funcional no hace que los programas sean más rápidos, como regla general. Lo que hace es una programación paralela y concurrente más fácil . Hay dos claves principales para esto:
- Evitar el estado mutable tiende a reducir la cantidad de cosas que pueden salir mal en un programa, y aún más en un programa concurrente.
- Evitar las primitivas de sincronización basada en bloqueo y memoria compartida a favor de conceptos de nivel superior tiende a simplificar la sincronización entre hilos de código.
Un excelente ejemplo del punto # 2 es que en Haskell tenemos una clara distinción entre paralelismo determinista versus concurrencia no determinista . No hay mejor explicación que citar el excelente libro de Simon Marlow Parallel and Concurrent Programming en Haskell (las citas son del Capítulo 1 ):
Un programa paralelo es aquel que utiliza una multiplicidad de hardware computacional (por ejemplo, varios núcleos de procesador) para realizar un cálculo más rápidamente. El objetivo es llegar a la respuesta antes, delegando diferentes partes de la computación a diferentes procesadores que se ejecutan al mismo tiempo.
Por el contrario, la concurrencia es una técnica de estructuración de programas en la que existen múltiples hilos de control. Conceptualmente, los hilos de control se ejecutan "al mismo tiempo"; es decir, el usuario ve sus efectos intercalados. Si realmente se ejecutan al mismo tiempo o no es un detalle de implementación; Un programa concurrente puede ejecutarse en un único procesador a través de la ejecución intercalada o en múltiples procesadores físicos.
Además de esto, Marlow también menciona la dimensión del determinismo :
Una distinción relacionada es entre modelos de programación deterministas y no deterministas . Un modelo de programación determinista es uno en el que cada programa puede dar un solo resultado, mientras que un modelo de programación no determinista admite programas que pueden tener resultados diferentes, dependiendo de algún aspecto de la ejecución. Los modelos de programación concurrente son necesariamente no deterministas porque deben interactuar con agentes externos que causan eventos en momentos impredecibles. Sin embargo, el no determinismo tiene algunos inconvenientes notables: los programas se vuelven significativamente más difíciles de probar y razonar.
Para la programación paralela, nos gustaría utilizar modelos de programación deterministas si es posible. Dado que el objetivo es llegar a la respuesta más rápidamente, preferimos no hacer que nuestro programa sea más difícil de depurar en el proceso. La programación paralela determinista es lo mejor de ambos mundos: las pruebas, la depuración y el razonamiento se pueden realizar en el programa secuencial, pero el programa se ejecuta más rápido con la adición de más procesadores.
En Haskell, las características de paralelismo y concurrencia están diseñadas en torno a estos conceptos. En particular, qué otros idiomas se agrupan como un conjunto de características, Haskell se divide en dos:
- Características deterministas y bibliotecas para paralelismo .
- Características y librerías no deterministas para concurrencia .
Si solo está tratando de acelerar un cálculo puro y determinista, tener paralelismo determinista a menudo facilita mucho las cosas. A menudo solo haces algo como esto:
- Escriba una función que produzca una lista de respuestas, cada una de las cuales es costosa de calcular pero no depende mucho la una de la otra. Se trata de Haskell, por lo que las listas son perezosos valores -las de sus elementos no se computan en realidad hasta que un consumidor lo exige.
- Use la biblioteca de Estrategias para consumir los elementos de las listas de resultados de su función en paralelo en múltiples núcleos.
De hecho, hice esto con uno de mis programas de proyectos de juguetes hace unas semanas . Fue trivial paralelizar el programa; lo clave que tuve que hacer fue, en efecto, agregar un código que diga "calcular los elementos de esta lista en paralelo" (línea 90), y obtuve un aumento de rendimiento casi lineal en Algunos de mis casos de prueba más caros.
¿Mi programa es más rápido que si hubiera utilizado las utilidades de subprocesos múltiples basadas en bloqueos convencionales? Lo dudo mucho. Lo bueno en mi caso fue obtener mucho de tan poco dinero: mi código es probablemente muy subóptimo, pero debido a que es tan fácil de paralelizar, obtuve una gran aceleración con mucho menos esfuerzo que perfilarlo y optimizarlo correctamente, y sin riesgo de condiciones de carrera. Y esa, diría, es la forma principal en que la programación funcional le permite escribir programas "más rápidos".