En règle générale, la programmation fonctionnelle ne permet pas d'accélérer les programmes. Cela facilite la programmation parallèle et simultanée. Il y a deux clés principales à cela:
- L'évitement de l'état mutable tend à réduire le nombre de problèmes pouvant survenir dans un programme, et encore plus dans un programme simultané.
- Le fait d'éviter les primitives de synchronisation en mémoire partagée et de verrouillage basées sur des concepts de niveau supérieur a tendance à simplifier la synchronisation entre les threads de code.
Un excellent exemple du point # 2 est que dans Haskell nous avons une distinction claire entre le parallélisme déterministe par rapport à la concurrence non déterministe . Il n'y a pas de meilleure explication que de citer l'excellent livre de Simon Marlow intitulé Parallel and Concurrent Programming in Haskell (les citations sont tirées du chapitre 1 ):
Un programme parallèle est un programme qui utilise une multiplicité de matériel informatique (plusieurs cœurs de processeur, par exemple) pour effectuer un calcul plus rapidement. L'objectif est d'arriver à la réponse plus tôt, en déléguant différentes parties du calcul à différents processeurs qui s'exécutent en même temps.
En revanche, la simultanéité est une technique de structuration de programme dans laquelle il existe plusieurs threads de contrôle. Conceptuellement, les threads de contrôle s'exécutent «en même temps»; c'est-à-dire que l'utilisateur voit leurs effets entrelacés. Qu'ils s'exécutent en même temps ou non en même temps est un détail d'implémentation; un programme simultané peut s'exécuter sur un seul processeur via une exécution entrelacée ou sur plusieurs processeurs physiques.
Marlow mentionne également la dimension du déterminisme :
Une distinction est établie entre les modèles de programmation déterministes et non déterministes . Un modèle de programmation déterministe est un modèle dans lequel chaque programme ne peut donner qu'un seul résultat, alors qu'un modèle de programmation non déterministe admet des programmes pouvant avoir des résultats différents, en fonction de certains aspects de l'exécution. Les modèles de programmation simultanés sont nécessairement non déterministes, car ils doivent interagir avec des agents externes qui provoquent des événements à des moments imprévisibles. Le non-déterminisme présente toutefois certains inconvénients notables: il devient de plus en plus difficile de tester et de raisonner les programmes.
Pour la programmation parallèle, nous aimerions utiliser des modèles de programmation déterministes dans la mesure du possible. Puisque l'objectif est simplement d'arriver à la réponse plus rapidement, nous préférons ne pas rendre notre programme plus difficile à déboguer au cours du processus. La programmation parallèle déterministe est le meilleur des deux mondes: les tests, le débogage et le raisonnement peuvent être effectués sur le programme séquentiel, mais le programme s'exécute plus rapidement avec l'ajout de processeurs supplémentaires.
Dans Haskell, les fonctionnalités de parallélisme et de concurrence sont conçues autour de ces concepts. Haskell se divise notamment en deux groupes de langues:
- Fonctionnalités déterministes et bibliothèques pour le parallélisme .
- Fonctions et bibliothèques non déterministes pour la simultanéité .
Si vous essayez simplement d'accélérer un calcul purement déterministe, le parallélisme déterministe facilite souvent beaucoup les choses. Souvent, vous faites juste quelque chose comme ça:
- Ecrivez une fonction qui produit une liste de réponses dont chacune coûte cher à calculer, mais ne dépend pas beaucoup l'une de l'autre. C'est Haskell, donc les listes sont paresseuses - les valeurs de leurs éléments ne sont pas réellement calculées jusqu'à ce qu'un consommateur les demande.
- Utilisez la bibliothèque Stratégies pour utiliser les éléments des listes de résultats de votre fonction en parallèle sur plusieurs cœurs.
C'est ce que j'ai fait avec l'un de mes programmes de projets de jouets il y a quelques semaines . Il était trivial de paralléliser le programme - l’essentiel, c’était d’ajouter du code indiquant «calculer les éléments de cette liste en parallèle» (ligne 90), et j’ai obtenu un gain de débit quasi linéaire en certains de mes cas de test les plus chers.
Mon programme est-il plus rapide que si j'avais utilisé des utilitaires classiques de multithreading basés sur des verrous? J'en doute fort. La chose intéressante dans mon cas était de tirer tellement de profit de mon argent - mon code est probablement très sous-optimal, mais parce qu'il est si facile de le paralléliser, j'ai beaucoup accéléré avec beaucoup moins d'effort que de le profiler et de l'optimiser correctement, et aucun risque de conditions de course. Et je dirais que c’est la manière principale dont la programmation fonctionnelle vous permet d’écrire des programmes "plus rapides".