programmation - haskell open classroom



Langage de programmation pour le parallélisme fonctionnel: F#vs Haskell (5)

La programmation fonctionnelle possède des structures de données immuables et aucun effet secondaire intrinsèquement adapté à la programmation parallèle. J'étudie comment exploiter le calcul multicœur dans un langage fonctionnel, et cibler le code de production pour certaines applications numériques.

F # a Microsoft derrière son dos, et ses constructions parallèles telles que PLINQ , TPL , Async Workflow ont été bien documentées et ont montré quelques potentiels. Cependant, la recherche sur le parallélisme dans Haskell est très active en ce moment, et elle possède de nombreuses fonctionnalités intéressantes qui n'ont pas encore été supportées par F #:

Ma question est la langue que je devrais choisir pour le parallélisme fonctionnel? Si F # est choisi, y a-t-il des pointeurs pour construire ce qu'ils ont actuellement dans Haskell?

METTRE À JOUR:

J'ai choisi la réponse de Simon, car elle a fait ressortir de bonnes discussions sur le garbage collector, l'allocation de mémoire et le manque de mémoire cache. Je vais m'en tenir à F #, et je pense que ces réponses me sont utiles pour étudier le parallélisme fonctionnel.


La réponse simple est que, parce que les deux langages ont un support solide pour le parallélisme et la concurrence, cela ne devrait pas être un facteur dans votre décision sur la langue à utiliser. Autrement dit, il y a des facteurs beaucoup plus importants à prendre en considération pour une décision comme celle-là.


Je vais être downvoted pour cela, mais laissez-moi être le curmudgeon.

Les langues fonctionnelles sont géniales. Ils modifient votre façon de penser les problèmes de décomposition, et ils correspondent incroyablement bien à certains types de problèmes. Chaque programmeur doit être familier avec au moins un langage de programmation fonctionnel. Mais "les langages fonctionnels sont intrinsèquement bons pour la programmation parallèle" n'est probablement pas la raison pour laquelle.

Il vaut la peine de noter que ce qui est incontestablement le langage fonctionnel parallèle le plus réussi de tous les temps, Erlang, utilise un passage de message complètement standard pour implémenter son parallélisme, et le lien entre sa nature fonctionnelle et son parallélisme est au mieux indirect.

Il y a vingt-cinq ans, les langages fonctionnels ont fait l'objet d'une forte poussée, car l'argument semblait alors très convaincant - les langages fonctionnels semblaient parfaitement adaptés aux architectures de plus en plus parallèles de l'époque. L'argument était que les compilateurs et les runtimes seraient automatiquement capables d'implémenter le parallélisme, en raison de la nature libre des effets secondaires des langages. SISAL , qui pouvait alors être compilé en exécutables à mémoire partagée et distribuée (!) A été développé à cette époque, tout comme Haskell, tout comme ML, le prédécesseur d'Objective CAML et les autres lanaguages ​​de la famille ML.

Tout cela est juste offert un peu de perspective historique. Depuis plus d'un quart de siècle, les défenseurs des langues fonctionnelles, y compris les plus brillants dans le domaine, ont dit que le jour des langues fonctionnelles au soleil était juste au coin de la rue, et qu'il allait être applicable au parallélisme C'est l'application de tueur. Et pourtant, nous sommes là, sans même avoir entendu parler de SISAL; et je devine que la plupart des lecteurs de ce post pensent à Haskell comme une nouvelle langue chaude.

Bien sûr, avec les considérations multicœurs, les choses sont devenues tellement urgentes que les langages fonctionnels vont vraiment briller, ou que cette année sera l'année où il y a une percée que je ne peux même pas imaginer qui change complètement le paysage. Cette année pourrait bien être différente de chacune des 25 années précédentes. Mais peut-être pas aussi.

La vaste, vaste, vaste majorité du code parallèle et simultané existant maintenant, et bien dans l'avenir prévisible, n'est pas écrit dans des langages fonctionnels. Si vous souhaitez en savoir plus sur le parallélisme, explorez les mécanismes disponibles dans F #, Haskell, etc .; mais ne vous limitez pas à ceux-là, c'est tout ce que je dis.


Tout d'abord, je suis d'accord avec les autres qu'il n'y a pas de réponse objective.

Cependant, je pense que l'idée de parallélisme fonctionnel est un peu surestimée. Sûrement, vous pouvez facilement trouver des dépendances de données dans votre programme et si vous traitez beaucoup de données, vous pouvez utiliser une bibliothèque data-parallel pour la paralléliser facilement et en toute sécurité. Cependant, cela peut être fait même en C # (en utilisant TPL et PLINQ) si vous êtes un peu prudent sur ce que vous écrivez.

Le problème est que la plupart des programmes n'ont pas besoin d'être parallélisés, car ils ne font tout simplement pas assez de travail CPU. Par exemple, F # async résout (je pense) le problème plus important de l'activation des E / S asynchrones, qui est la raison de la plupart des "blocages" dans les applications connectées. Je pense que la popularité de Node.js démontre assez bien cette importance.

La vraie valeur des langages fonctionnels est dans l'expressivité du langage - vous pouvez facilement définir des abstractions pour votre problème, écrire du code d'une manière plus succincte, plus facile à comprendre, raisonner et tester. Vous obtenez ceci dans F # et Haskell.

Pour répondre à votre question spécifique sur le parallélisme - je crois que le statut du support du parallélisme en F # est plus stable (mais alors, je suis une personne F #). Vous pouvez choisir entre asynchrone, TPL et agents F # (inspirés d'Erlang) (qui sont toutes des bibliothèques assez stables). Du côté de Haskell, il y a encore beaucoup d'évolution en cours. Le travail le plus récent n'a que quelques semaines. Je trouve également qu'il est plus facile d'utiliser le parallélisme dans une langue avec un modèle d'évaluation clairement spécifié, mais ce n'est peut-être que ma préférence personnelle.


La programmation fonctionnelle a des structures de données immuables et aucun effet secondaire qui est intrinsèquement adapté à la programmation parallèle.

C'est une idée fausse commune. Le parallélisme concerne uniquement la performance et la pureté dégrade les performances. La programmation purement fonctionnelle n'est donc pas un bon point de départ si votre objectif est d'obtenir des performances décentes. En général, la pureté signifie plus d'allocation et pire localité. En particulier, les structures de données purement fonctionnelles remplacent les tableaux par des arbres, ce qui implique beaucoup plus d'allocations et impose beaucoup plus de contraintes au ramasse-miettes.

Par exemple, mesurez la performance de l'élégant "quicksort" purement fonctionnel dans Haskell . La dernière fois que j'ai vérifié, c'était des milliers de fois plus lent que la solution impérative conventionnelle sur ma machine.

En outre, personne n'a jamais réussi à implémenter une structure de données de dictionnaire efficace (pure ou impure) ou un tri purement fonctionnel efficace dans Haskell et personne n'a trouvé comment écrire une structure de données d'ensembles disjoints persistants asymptotiquement efficace et il n'existe aucun moyen connu de implémenter d'autres structures de données de base comme des dictionnaires faibles fonctionnels!

De plus, bien qu'il soit théoriquement possible d'écrire du code impur dans Haskell, le GC est fortement optimisé pour le code pur au détriment de la performance de la mutation. Par exemple, la table de hachage de GHC est toujours 26 fois plus lente que celle de .NET . Historiquement, la performance de la mutation était considérée comme si peu importante dans Haskell que l'écriture d'un seul pointeur vers un tableau était une opération O ( n ) dans GHC depuis cinq ans .

J'étudie comment exploiter le calcul multicœur dans un langage fonctionnel, et cibler le code de production pour certaines applications numériques.

La meilleure façon que j'ai trouvée est d'apprendre à écrire des programmes parallèles décents pour les multicores dans le style impératif (étudier Cilk , en particulier) et ensuite factoriser le code en utilisant des fonctions de première classe et l'élimination des queues dans le style fonctionnel impur .

Cela signifie des structures de données et des algorithmes inconsistants dans le cache. Personne n'a jamais fait ça à Haskell. En effet, aucune des recherches publiées à ce jour sur Haskell n'a même mentionné le concept essentiel de la complexité du cache . En outre, bien qu'il soit largement connu qu'une évaluation non stricte (paresseuse) rend la consommation d'espace imprévisible, il n'est pas encore largement reconnu que ce même problème rend l'évolutivité extrêmement imprévisible sur les multicœurs.

F # a Microsoft derrière son dos, et ses constructions parallèles telles que PLINQ, TPL, Async Workflow ont été bien documentées et ont montré quelques potentiels.

Ils vont bien au-delà du potentiel. Des milliers d'applications commerciales sont construites sur ces fondations industrielles.

Cependant, la recherche sur le parallélisme dans Haskell est très active en ce moment, et elle possède de nombreuses fonctionnalités intéressantes qui n'ont pas encore été supportées par F #:

Pourquoi supposez-vous que ce sont de "belles fonctionnalités"?

Je suggère de lire le dernier article de Simon Marlow à ce sujet:

"... une combinaison d'expérience pratique et d'investigation nous a amenés à conclure que cette approche n'est pas sans inconvénients.En résumé, le problème est le suivant: réaliser le parallélisme avec le par exige que le programmeur comprenne les propriétés opérationnelles du langage qui sont à meilleure implémentation-définie (et au pire indéfinie) Cela rend l'utilisation difficile, et les pièges abondent - les nouveaux utilisateurs ont un taux d'échec élevé ... "

Ma question est la langue que je devrais choisir pour le parallélisme fonctionnel?

Je déconseille le code purement fonctionnel parallèle pour la production car c'est un joker complet. En supposant que vous êtes heureux de sacrifier une certaine pureté afin d'atteindre des performances compétitives, j'utilise et recommande F #.


Si le type de code que vous avez en tête alloue beaucoup de mémoire, vous pouvez constater que le garbage collector GHC est plus évolutif que le garbage collector .NET. Il existe des preuves anedcodales que le GC .NET devient un goulot d'étranglement lorsque plusieurs threads allouent beaucoup, et c'est aussi une épine dans le pied de la plupart des collecteurs Java. D'un autre côté, nous avons accordé beaucoup d'attention à l'obtention d'une bonne localisation et évolutivité dans le garbage collector de GHC - principalement parce que nous n'avons pas le choix, la plupart des codes idiomatiques Haskell allouent lourdement de toute façon. J'ai des benchmarks qui allouent comme des fous et gardent l'échelle au-delà de 24 cœurs.

Dans Haskell, notez que vous obtenez une garantie de déterminisme du système de type, que vous n'obtenez pas dans F #.

Vous avez mentionné Data Parallel Haskell: une mise en garde ici, il n'est pas encore prêt pour la production, bien que l'équipe de DPH s'attend à ce que la prochaine version de GHC 7.2.1 ait une implémentation DPH stable.





parallel-processing