Estimation de Pi en ruby multithread
AKA j’ai envie de parler de la gem parallel
Aujourd’hui, j’avais envie de jouer avec la gem parallel, qui permet d’effectuer des traitements en parallèle sur des collections sans se soucier de gérer l’ordonnancement de ses threads.
Un problème qui se prête plutôt bien à une mise en parallèle est l’estimation de Pi par la méthode Monte Carlo.
Estimation de Pi
La méthode Monte Carlo d’estimation de Pi utilise le calcul de l’aire d’un cercle pour estimer une approximation de Pi par tâtonnement statistique.
En bref, on trace un cercle de rayon 1 dans un carré de coté 1, on tire aléatoirement un grand nombre de points à l’intérieur du carré, et on fait le rapport entre le nombre de points dans le cercle et à l’extérieur du cercle, multiplié par 4.
Pour calculer le nombre de points à l’intérieur du cercle, on peut utiliser la méthode suivante:
Si l’on veut tester notre méthode de suite, en tentant de projeter 60 millions de points aléatoirement sur notre carré, on peut utiliser:
Ce qui nous donne:
Comment la gem parallel peut-elle nous aider ?
La raison pour laquelle cet algorithme se prête particulièrement bien à une mise en parallèle est que les résultats des différentes itérations ne sont pas interdépendants (contrairement à une suite de Fibonacci, ou il faut utiliser les valeurs des itération n-1 et n-2 pour calculer la valeur de l’itération n).
Avec parallel, on peut, au choix:
- paralléliser un appel à la méthode map de ruby avec Parallel.map
- effectuer en parallèle des opérations sur chaque élément d’une collection avec Parallel.each
Dans notre cas, on peut essayer, par exemple, de découper en 6 tranches de 10 millions de projections de points nos 60 millions de tentatives:
Ici, on traite en parallèle six tranches de 10 millions de projection de points, et on additionne ensuite les six résultats (le nombre de points à l’intérieur du cercle pour chacune de ces six tranches de projections).
Le résultat parle par lui même:
On peut noter que le temps total d’exécution est tombé de 15.51 secondes à 4.32 secondes (mais que le temps user est lui monté de 15.47 secondes à 24.55 secondes).
Conclusion
La gem parallel est un moyen rapide et efficace de facilement paralléliser des traitements en ruby. Néanmoins, suite à quelques essais avec des collègues, il est apparu que la gem pouvait poser des problèmes avec certaines ressources.
Avec rails, par exemple, pour pouvoir traiter en parallèle des opérations nécessitant un accès à une ressource active record, il est nécessaire de forcer une réouverture de connexion à la base:
Ce qui en fait un formidable outil pour mettre en place rapidement des traitements d’opérations en parallèle lorsqu’on ne fait rien de particulier avec des ressources partagées.