Les compilateurs comme Javac détectent-ils automatiquement les fonctions pures et les parallélisent-ils?

12

Les fonctions pures sont connues pour faciliter la parellisation. En quoi la programmation fonctionnelle la rend-elle intrinsèquement adaptée à l'exécution parallèle?

Les compilateurs tels que Javac sont-ils assez intelligents pour détecter quand une méthode est une fonction pure? On peut toujours implémenter des classes qui implémentent des interfaces fonctionnelles telles que Function , mais ont des effets secondaires.

Naveen
la source
7
La question n'est pas seulement de savoir si le compilateur peut savoir si une fonction est pure, mais aussi s'il peut planifier intelligemment l'exécution parallèle de fonctions pures. Il ne suffit pas de lancer un nouveau thread pour chacun: c'est inefficace. GHC (Haskell) gère ce problème en utilisant la paresse et les «fils verts»; Je serais honnêtement surpris si un langage impur était même essayé, étant donné la difficulté supplémentaire de s'assurer que les threads purs étaient correctement programmés par rapport au thread impur principal.
Ryan Reich
@RyanReich, y a-t-il des gains de performances à utiliser la programmation fonctionnelle dans un langage fonctionnel impur comme Java? les gains de la programmation fonctionnelle sont-ils purement fonctionnels comme la modularité?
Naveen
@RyanReich GHC résout le problème en faisant annoter le programmeur lorsqu'il souhaite le parallélisme. La pureté implique que ces annotations ne changent jamais la sémantique, juste les performances. (Il existe également des mécanismes de simultanéité qui peuvent donner lieu au parallélisme, mais il s'agit d'une autre marmite de poisson.)
Derek Elkins a quitté le SE
@Naveen Il existe d'autres avantages aux fonctions pures en ce qui concerne l'optimisation en plus du parallélisme, comme une plus grande liberté de réorganisation du code, la mémorisation et l'élimination de la sous-expression commune. Je peux me tromper, mais je doute que javac tente de détecter la pureté, car c'est probablement assez rare dans le code idiomatique et quelque peu difficile pour tous sauf les cas les plus triviaux. Par exemple, vous devez savoir qu'il n'y aura pas de par NullPointerException. Les avantages des optimisations basées sur cela sont également probablement assez faibles pour les applications Java typiques.
Derek Elkins a quitté SE le
6
javac est le compilateur java, qui prend le code source java et génère des fichiers de classe de code d'octet java. Il est assez limité quant à ce qu'il peut (et est supposé faire). Il n'a pas la liberté ni les mécanismes sous-jacents nécessaires pour introduire le parallélisme dans le fichier de classe de code octet.
Erik Eidt

Réponses:

33

sont des compilateurs tels que Javac assez intelligents pour détecter quand une méthode est une fonction pure.

Il ne s'agit pas de "suffisamment intelligent". C'est ce qu'on appelle l' analyse de pureté et cela est impossible dans le cas général: cela équivaut à résoudre le problème de l'arrêt.

Maintenant, bien sûr, les optimiseurs font des choses prouvablement impossibles tout le temps, "prouvablement impossible dans le cas général" ne signifie pas qu'il ne fonctionne jamais, cela signifie seulement qu'il ne peut pas fonctionner dans tous les cas. Donc, il existe en fait des algorithmes pour vérifier si une fonction est pure ou non, c'est juste que le plus souvent le résultat sera "je ne sais pas", ce qui signifie que pour des raisons de sécurité et d'exactitude, vous devez supposer que cette fonction particulière pourrait être impure.

Et même dans les cas où elle exerce ses travaux, les algorithmes sont complexes et coûteux.

C'est donc le problème n ° 1: cela ne fonctionne que pour des cas spéciaux .

Problème n ° 2: bibliothèques . Pour qu'une fonction soit pure, elle ne peut appeler que des fonctions pures (et ces fonctions ne peuvent appeler que des fonctions pures, et ainsi de suite). Javac ne connaît évidemment que Java et ne connaît que le code qu'il peut voir. Donc, si votre fonction appelle une fonction dans une autre unité de compilation, vous ne pouvez pas savoir si elle est pure ou non. S'il appelle une fonction écrite dans une autre langue, vous ne pouvez pas le savoir. S'il appelle une fonction dans une bibliothèque qui n'est peut-être même pas encore installée, vous ne pouvez pas le savoir. Etc.

Cela ne fonctionne que lorsque vous avez une analyse complète du programme, lorsque le programme entier est écrit dans la même langue et que tout est compilé en une seule fois. Vous ne pouvez utiliser aucune bibliothèque.

Problème n ° 3: planification . Une fois que vous avez déterminé quelles parties sont pures, vous devez toujours les planifier sur des threads séparés. Ou pas. Le démarrage et l'arrêt des threads sont très coûteux (en particulier en Java). Même si vous conservez un pool de threads et ne les démarrez pas ou ne les arrêtez pas, le changement de contexte de thread est également coûteux. Vous devez être sûr que le calcul s'exécutera beaucoup plus longtemps que le temps nécessaire pour planifier et changer de contexte, sinon vous perdrez les performances, pas les gagnerez.

Comme vous l'avez probablement deviné maintenant, déterminer combien de temps un calcul prendra est impossible dans le cas général (nous ne pouvons même pas déterminer si cela prendra un temps fini, sans parler du temps) et difficile et coûteux même dans le cas particulier.

A côté: Javac et optimisations . Notez que la plupart des implémentations de javac n'effectuent pas réellement beaucoup d'optimisations. La mise en œuvre d'Oracle de javac, par exemple, repose sur le moteur d'exécution sous-jacent pour effectuer des optimisations . Cela conduit à un autre ensemble de problèmes: disons, javac a décidé qu'une fonction particulière est pure et elle est assez chère, et donc elle la compile pour être exécutée sur un thread différent. Ensuite, l'optimiseur de la plate-forme (par exemple, le compilateur HotSpot C2 JIT) arrive et optimise l'ensemble de la fonction. Maintenant, vous avez un fil vide qui ne fait rien. Ou, imaginez, encore une fois, javac décide de planifier une fonction sur un autre thread, et l'optimiseur de plateforme pourrait optimisez-le complètement, sauf qu'il ne peut pas effectuer d'inlining au-delà des limites de thread, et donc une fonction qui pourrait être complètement optimisée est maintenant exécutée inutilement.

Donc, faire quelque chose comme ça n'a de sens que si vous avez un seul compilateur effectuant la plupart des optimisations en une seule fois, afin que le compilateur connaisse et puisse exploiter toutes les différentes optimisations à différents niveaux et leurs interactions les unes avec les autres.

Notez que, par exemple, le compilateur JIT HotSpot C2 effectivement fait effectuer une auto-vectorisation, qui est aussi une forme d'auto-parallélisation.

Jörg W Mittag
la source
Eh bien, selon votre définition de "fonction pure", l'utilisation de fonctions impures dans l'implémentation peut être autorisée.
Déduplicateur
@Deduplicator Eh bien, selon votre définition de definition, l'utilisation d'un disparate definitionde purityest probablement obscure
cat
1
Votre problème # 2 est principalement invalidé par le fait que pratiquement toutes les optimisations sont exécutées par le JIT (vous le savez évidemment, mais ignorez-le). De même, le problème # 3 est partiellement invalidé car JIT s'appuie fortement sur les statistiques recueillies par l'interprète. Je suis particulièrement en désaccord avec "Vous ne pouvez utiliser aucune bibliothèque" car il y a désoptimisation à la rescousse. Je suis d'accord que la complexité supplémentaire serait un problème.
maaartinus
2
@maaartinus: D'ailleurs, seule la toute fin de ma réponse est spécifique à javac. Je spécifiquement faire mention, par exemple, que « Cela ne fonctionne que lorsque vous avez une analyse tout le programme, lorsque l'ensemble du programme est écrit dans la même langue, et tout est compilé à la fois en une seule fois. » C'est évidemment vrai pour C2: il ne traite que d'une seule langue (bytecode JVM), et il a accès à tout le programme à la fois.
Jörg W Mittag
1
@ JörgWMittag Je sais que l'OP pose des questions sur javac, mais je parie qu'ils supposent que javac est la chose responsable des optimisations. Et qu'ils savent à peine qu'il y a C2. Je ne dis pas, votre réponse est mauvaise. C'est juste que laisser javac faire une optimisation (sauf pour des trivialités comme l'utilisation StringBuilder) est un non-sens, donc je le rejetterais et supposerais simplement, l'OP écrit javac mais signifie Hotspot. Votre problème # 2 est une très bonne raison contre l'optimisation de quoi que ce soit dans javac.
maaartinus
5

La réponse votée n'a pas noté une chose. La communication synchrone entre les threads est extrêmement coûteuse. Si la fonction est capable d'être exécutée à un rythme de plusieurs millions d'appels par seconde, cela vous fait plus de mal de la paralléliser plutôt que de la laisser telle quelle.

La forme la plus rapide de communication synchrone entre threads, utilisant des boucles occupées avec des variables atomiques, est malheureusement peu énergivore. Si vous devez recourir à des variables de condition pour économiser de l'énergie, les performances de votre communication inter-threads en souffrent.

Ainsi, le compilateur n'a pas seulement besoin de déterminer si une fonction est pure, il devrait également estimer le temps d'exécution de la fonction pour voir si la parallélisation est un gain net. En outre, il devrait choisir entre des boucles occupées utilisant des variables atomiques ou des variables de condition. Et il faudrait créer des fils derrière votre dos.

Si vous créez les threads dynamiquement, c'est encore plus lent que d'utiliser des variables de condition. Ainsi, le compilateur devra configurer un certain nombre de threads déjà en cours d'exécution.

Donc, la réponse à votre question est non , les compilateurs ne sont pas assez "intelligents" pour auto-paralléliser des fonctions pures, en particulier dans le monde Java. Ils sont intelligents en ne les parallélisant pas automatiquement!

juhist
la source
5
" Ils sont intelligents en ne les parallélisant pas automatiquement! " : Cela va trop loin. S'il est vrai que la parallélisation à chaque point possible juste pour lui-même serait généralement inefficace, un compilateur intelligent identifierait une stratégie de parallélisation pratique. Je pense que la plupart des gens comprennent cela, donc lorsque nous parlons d'auto-parallélisation, nous voulons dire auto-pratique-parallélisation.
Nat
@Nat: ridiculement trop dur. Cela nécessiterait d'identifier des fonctions pures sur l'échelle d'exécution de 100s de millisecondes, et s'attendre à ce que le compilateur ait une idée de l'exécution des boucles qui n'ont pas de constantes dans leurs itérations (et les cas que vous ne voulez pas) est idiot.
Joshua
Je suis d'accord - le commentaire de @ Nat implique que la parallélisation ne signifie pas nécessairement plusieurs threads, ce qui est vrai. Le JIT pourrait, par exemple, aligner plusieurs appels vers une fonction pure et entrelacer leurs instructions CPU dans certains cas. Par exemple, si les deux appels de méthode récupèrent une constante, elle peut être récupérée une fois et conservée dans un registre CPU pour les deux instances de la méthode à utiliser. Les processeurs modernes sont des bêtes avec de nombreux registres à usage général et des instructions spécialisées qui peuvent être très utiles lors de l'optimisation du code.
1
@Joshua: Beaucoup plus facile en effet pour un compilateur JIT. Le compilateur JIT peut également comprendre qu'une fonction peut ne pas être pure, mais aucun appel n'a jusqu'à présent invoqué un comportement impur.
gnasher729
Je suis d'accord avec @Joshua. J'ai un algorithme difficile à paralléliser au travail. J'ai essayé de le paralléliser manuellement, même en faisant quelques approximations simplificatrices (et donc en modifiant l'algorithme), et j'ai misérablement échoué à chaque fois. Même un programme indiquant s'il est possible de paralléliser quelque chose est extrêmement difficile, même s'il serait beaucoup plus simple que de le paralléliser. N'oubliez pas que nous parlons de langages de programmation complets de Turing.
juhist