12
votes

Avantages des compilateurs pour les langages fonctionnels sur les compilateurs pour les langues impératives

Un suivi de cette question Quels sont les avantages de l'immuabilité intégrée de F # sur C #? -M I CORRECT dans la mesure où le compilateur F # peut faire certaines optimisations sachant qu'il s'agit d'un code largement immuable? Je veux dire même si un développeur écrit "fonctionnel c #" le compilateur ne saurait pas toute l'immuabilité que le développeur avait essayé de coder de manière à ne pas faire les mêmes optimisations, non?

En général, le compilateur d'une langue fonctionnelle serait capable de faire des optimisations qui ne seraient pas possibles avec une langue impérative - même une écrite avec autant d'immutabilité que possible?


3 commentaires

Lorsque vous êtes sûr qu'il n'y a pas d'effets secondaires dans les fonctions, il est beaucoup plus facile d'utiliser un prouveur de théorème pour réduire une composition de fonction à une version optimisée équivalente mathématiquement. Lorsque tout dans le monde change, vous ne pouvez pas être aussi agressif pour l'optimisation. Vous pouvez changer la signification d'un programme si vous ne faites pas attention.


Je pensais que vous demandiez l'avantage d'écrire un compilateur dans une langue fonctionnelle vs une langue impérative. Ha!


Merci à tous pour les réponses informatives.


6 Réponses :


5
votes

Oui, si vous ne considérez pas F #, mais envisagez HASKELL par exemple. Le fait qu'il n'existe pas d'effets secondaires ouvre vraiment beaucoup de possibilités d'optimisation.

Par exemple, considère dans une langue similaire: xxx

si une méthode correspondante était Écrit à Haskell, il y aurait aucun deuxième appel à la factorielle lorsque vous appelez FactorialUser. Il pourrait être possible de le faire en C #, mais je doute que les compilateurs actuels le font, même pour un exemple simple comme cela. Comme les choses deviennent plus compliquées, il serait difficile pour C # compilateurs d'optimiser au niveau Haskell peut faire.

Note, F # n'est pas vraiment une langue fonctionnelle "pure", actuellement. Donc, j'ai apporté Haskell (ce qui est génial!).


5 commentaires

L'optimisation de la récursion de la queue est triviale, elle est gérée par le compilateur JIT. Ce qui pourrait s'en soucier moins sur Haskell ou les langues fonctionnelles. Pouvez-vous trouver un meilleur exemple?


Je ne parle pas de l'optimisation de la factorielle. Je parle de l'optimisation de FactorialUser pour faire un seul appel à la factorielle. Si la récursion de la queue était mon point de vue, quelle est l'utilisation de la rédaction de la factorialuser et de parler de cela?


@Moron cependant, dans ce cas, personne qui est habitué à la programmation impérative écrirait en réalité un code comme celui-ci et appelez la factorielle (M) deux fois. Ils l'affectaient simplement à une variable temporaire à l'intérieur de la factorielle () puis de la place. Je prends votre problème cependant, lorsqu'il est appliqué dans un contexte plus large.


@Trevor: correct. C'était juste un exemple, cependant. Il existe d'autres possibilités telles que la réorganisation, etc. Il pourrait même y avoir des possibilités d'optimisation de l'exécution (comme mémoire de mémoisation).


@nobugz: Il n'y a pas de récursion queue ici (comme l'a dit Moron).



7
votes

NO.

Le compilateur F # ne permet aucune tentative d'analyser la transparence référentielle d'une méthode ou de Lambda. Le .NET BCL n'est tout simplement pas conçu pour cela.

La spécification de langue F # réserve le mot-clé «pur», ce qui marquait manuellement une méthode que PURE peut être possible dans VNEXT, permettant une réduction de graphique plus agressive des expressions Lambda.

Toutefois, si vous utilisez les types d'enregistrement ou algébrique, F # créera des opérateurs de comparaison et d'égalité par défaut et fournira une sémantique de copie. Parmi de nombreux autres avantages (assujettie à la correspondance de modèles, au milieu du monde fermé), cela réduit un fardeau important!


0 commentaires

3
votes

Malheureusement, parce que F # n'est que surtout pur, il n'y a pas vraiment de nombreuses possibilités d'optimisation agressive. En fait, il y a quelques endroits où F # "pessimise" Code par rapport à c # (par exemple, faire des copies défensives des structures pour empêcher la mutation observable). Du côté lumineux, le compilateur fait un bon travail dans l'ensemble malgré cela, offrant une performance comparable à C # dans la plupart des endroits tout en faisant de manière significative des programmes plus faciles à raisonner.


0 commentaires

2
votes

Des optimisations supplémentaires pour les langues fonctionnelles sont parfois possibles, mais pas nécessairement à cause de l'immuabilité. En interne, de nombreux compilateurs convertissent du code en une forme SSA (attribution statique unique), où chaque variable locale à l'intérieur d'une fonction ne peut être attribuée qu'une seule fois. Cela peut être fait pour les langues impératives et fonctionnelles. Par exemple: xxx

peut devenir xxx

x_0 et x_1 sont différents noms variables. Cela simplifie considérablement de nombreuses transformations, car vous pouvez déplacer des morceaux de code sans vous soucier de la valeur qu'ils ont à des points spécifiques dans le programme. Cela ne fonctionne pas pour les valeurs stockées en mémoire (c'est-à-dire des globaux, des valeurs de tas, des tableaux, etc.). Encore une fois, cela se fait pour les langues fonctionnelles et impératives.

Un avantage que la plupart des langues fonctionnelles fournissent est un système de type fort. Cela permet au compilateur de faire des hypothèses qu'elle ne pourraient pas autrement. Par exemple, si vous avez deux références de types différents, le compilateur sait qu'ils ne peuvent pas alias (point à la même chose). Ce n'est pas une hypothèse qu'un compilateur C pourrait jamais faire.


1 commentaires

Un compilateur C pourrait légalement faire cette hypothèse, selon la norme. Selon la "règle d'aliasing stricte", deux pointeurs, de différents types, ne sont pas autorisés à pointer vers la même adresse. Discussion de la stricte règle d'aliasing.



2
votes

Je dirais largement "non".

Les principaux avantages «optimisation» que vous obtenez de l'immutabilité ou de la transparence référentielle sont des éléments comme la possibilité de faire «l'élimination de sous-experts courante» lorsque vous voyez du code comme ... F (x) ... F (x) ... . Mais une telle analyse est difficile à faire sans informations très précises, et puisque F # exécute sur le .NET Runtime et .NET n'a aucun moyen de marquer des méthodes aussi pures (sans effet), elle nécessite une tonne d'informations et d'une analyse intégrées à Essayez même de faire tout cela.

D'autre part, dans une langue comme Haskell (qui signifie principalement «haskell», comme il y a peu de langues «comme Haskell» que quiconque a entendu ou utilise :)) c'est paresseux et pur, l'analyse est plus simple. (Tout est pur, va noix).

Cela dit, ces «optimisations» peuvent souvent interagir mal avec d'autres aspects utiles du système (prévisibilité des performances, débogage, ...).

Il y a souvent des histoires de "un compilateur suffisamment intelligent pourraient faire x", mais mon opinion est que le "compilateur suffisamment intelligent" est, et sera toujours, un mythe. Si vous voulez un code rapide, écrivez un code rapide; Le compilateur ne va pas vous sauver. Si vous souhaitez une élimination commune de sousexpression, créez une variable locale (faites-la vous-même).

C'est surtout mon opinion, et vous êtes invité à la bowevote ou en désaccord (en effet, j'ai entendu "Multicore" suggéré comme une raison croissante que potentiellement "optimisation pourrait être sexy à nouveau", ce qui semble plausible sur la face de celui-ci ). Mais si vous espérez toujours que tout compilateur effectuant une optimisation non triviale (qui n'est pas pris en charge par des annotations dans le code source), soyez prêt à attendre une longue période de temps pour que vos espoirs soient remplies.

Ne vous méprenez pas - l'immuabilité est bonne et est susceptible de vous aider à écrire un code «rapide» dans de nombreuses situations. Mais pas parce que le compilateur l'optimise - plutôt, car le code est facile à écrire, déboguer, obtenir correctement, paralléliser, profiler et décider quels sont les goulots d'étranglement les plus importants pour passer du temps sur (éventuellement les réécrire mutabilité). Si vous souhaitez un code efficace, utilisez un processus de développement qui vous permet de développer, de tester et de profiler rapidement.


4 commentaires

Merci Brian; C'est exactement le genre d'explication que j'espérais. Je suppose que, en raison de la plus grande immuabilité, le compilateur serait en mesure de faire des optimisations qui ne seraient pas possibles pour la compilation du code impératif. De toute évidence, je faisais une mauvaise hypothèse.


Mais c'est un avantage. Vous pouvez être paresseux et écrire du code aussi facilement que possible avec les connaissances que le compilateur peut effectuer de telles optimisations de manière triviale. Je préférerais ne pas écrire la sous-exposée commune moi-même dans la plupart des cas.


Je pense qu'il y a une lacune aussi large qu'un océan entre les «optimisations qu'un compilateur peut faire de manière triviale» et des optimisations de votre compilateur.


Il convient de noter que l'élimination commune de subexpression est plus difficile dans HASKELL, pas plus facile, car il suffit d'éliminer les subexpressions communes peut entraîner des fuites spatiales. La détection lorsque le CSE conduit à des fuites spatiales est difficile, tandis que déterminer si une fonction dans une langue impure est pure est simple (calculer une bonne approximation à pure / impure est simple, c'est-à-dire).



20
votes

suis-je correct en supposant que le compilateur F # peut faire certains optimisations sachant que cela traite avec un code largement immuable?

Malheureusement pas. À un écrivain du compilateur, il y a une énorme différence entre "largement immuable" et "immuable". Même l'immuabilité garantie n'est pas si importante pour l'optimiseur; La principale chose que cela vous achète est que vous pouvez écrire un Inflener très agressif .

En général, le compilateur d'une langue fonctionnelle serait capable de faire des optimisations qui ne seraient pas possibles avec une langue impérative - même une écrite avec autant d'immutabilité que possible?

Oui, mais c'est surtout une question de pouvoir appliquer plus facilement les optimisations classiques, dans plus de lieux. Par exemple, l'immuabilité facilite la demande d'appliquer Élimination commune-sousexpression car l'immuabilité peut vous garantir que le contenu de certaines cellules de mémoire n'est pas modifié.

D'autre part, si votre langue fonctionnelle n'est pas simplement immuable mais pure (aucun effet secondaire comme E / S), vous permettez à une nouvelle classe d'optimisations impliquant une réécriture des expressions de niveau source à des expressions plus efficaces. L'une des plus importantes et plus intéressantes à lire est déforestation de coupe courte , ce qui est un moyen d'éviter l'affectation de l'espace mémoire pour les résultats intermédiaires. Un bon exemple à lire est flux Fusion .

Si vous compilez une langue fonctionnelle typée de manière statique, ce sont quelques-uns des points principaux de l'accent:


0 commentaires