web-dev-qa-db-fra.com

Efficacité de la programmation purement fonctionnelle

Est-ce que quelqu'un sait quel est le pire ralentissement asymptotique possible pouvant survenir lors d'une programmation purement fonctionnelle par opposition à impérativement (c'est-à-dire en permettant des effets secondaires)?

Clarification du commentaire de itowlson: existe-t-il un problème pour lequel le meilleur algorithme non destructif connu est asymptotiquement pire que le meilleur algorithme destructif connu, et si oui de combien?

395
Opt

Selon Pippenger [1996] , lorsque l'on compare un système LISP purement fonctionnel (et ayant une sémantique d'évaluation stricte, pas paresseux) à un système capable de muter des données, un algorithme écrit pour le LISP impur fonctionnant dans O ( n ) peut être traduit en un algorithme du LISP pur qui s'exécute en O ( n log n ) temps (d'après le travail effectué par Ben-Amram et Galil [1992] à propos de la simulation mémoire à accès aléatoire utilisant uniquement des pointeurs). Pippenger établit également qu'il existe des algorithmes pour lesquels c'est le mieux que vous puissiez faire; il y a des problèmes qui sont O ( n ) dans le système impur qui sont Ω ( n log n ) dans le système pur.

Il y a quelques mises en garde à propos de ce document. Le plus important est qu'il ne traite pas les langages fonctionnels paresseux, tels que Haskell. Bird, Jones et De Moor [1997] démontrent que le problème construit par Pippenger peut être résolu dans un langage fonctionnel paresseux en O ( n ) temps, mais ils n'établissent pas (et pour autant que je sache, personne ne l'a encore fait) si un langage fonctionnel paresseux peut résoudre tous les problèmes dans le même temps de fonctionnement asymptotique qu'un langage avec mutation.

Le problème construit par Pippenger nécessite que Ω ( n log n ) soit spécifiquement construit pour atteindre ce résultat, et n’est pas nécessairement représentatif des problèmes concrets du monde réel. Il existe quelques restrictions sur le problème qui sont un peu inattendues, mais nécessaires pour que la preuve fonctionne; en particulier, le problème nécessite que les résultats soient calculés en ligne, sans pouvoir accéder à une entrée future, et que l'entrée consiste en une séquence d'atomes provenant d'un ensemble non limité d'atomes possibles, plutôt que d'un ensemble de taille fixe. Et le document n'établit que les résultats (borne inférieure) pour un algorithme impur du temps d'exécution linéaire; pour les problèmes nécessitant une durée d'exécution plus longue, il est possible que le facteur supplémentaire O (log n ) observé dans le problème linéaire puisse être " absorbé "dans le processus des opérations supplémentaires nécessaires pour les algorithmes avec des temps d'exécution plus longs. Ces clarifications et questions ouvertes sont explorées brièvement par Ben-Amram [1996] .

En pratique, de nombreux algorithmes peuvent être implémentés dans un langage fonctionnel pur avec la même efficacité que dans un langage avec des structures de données mutables. Pour une bonne référence sur les techniques à utiliser pour mettre en œuvre efficacement des structures de données purement fonctionnelles, voir Structures de données purement fonctionnelles de Chris Okasaki [Okasaki 1998] (qui est une version développée de sa thèse [ Okasaki 1996] ).

Quiconque ayant besoin d'implémenter des algorithmes sur des structures de données purement fonctionnelles devrait lire Okasaki. Vous pouvez toujours obtenir au pire un ralentissement O (log n ) par opération en simulant une mémoire mutable avec un arbre binaire équilibré, mais dans de nombreux cas, vous pouvez le faire. Okasaki décrit de nombreuses techniques utiles, allant des techniques amorties aux techniques en temps réel qui effectuent le travail amorti progressivement. Les structures de données purement fonctionnelles peuvent être un peu difficiles à utiliser et à analyser, mais elles offrent de nombreux avantages, tels que la transparence référentielle, utiles pour l'optimisation du compilateur, l'informatique parallèle et distribuée, ainsi que pour la mise en œuvre de fonctionnalités telles que le contrôle de version, l'annulation et l'annulation.

Notez également que tout cela ne traite que des temps d'exécution asymptotiques. De nombreuses techniques pour la mise en œuvre de structures de données purement fonctionnelles entraînent un certain ralentissement constant des facteurs, du fait de la tenue de livres supplémentaire nécessaire à leur fonctionnement, ainsi que des détails de mise en œuvre du langage en question. Les avantages des structures de données purement fonctionnelles peuvent compenser ces ralentissements constants des facteurs. Vous devrez donc généralement faire des compromis en fonction du problème en question.

Références

527
Brian Campbell

Il existe en effet plusieurs algorithmes et structures de données pour lesquels on ne connaît aucune solution purement fonctionnelle asymptotiquement efficace (celle implémentée dans le lambda calcul pur), même avec de la paresse.

  • La susdite union-find
  • Tables de hachage
  • Tableaux
  • Quelques algorithmes de graphes
  • ...

Cependant, nous supposons que dans les langages "impératifs", l'accès à la mémoire est O(1)), alors qu'en théorie, cela ne peut pas être aussi asymptotique (c'est-à-dire pour des problèmes sans limites) et l'accès à Les données volumineuses sont toujours O (log n), ce qui peut être émulé dans un langage fonctionnel.

De plus, nous devons nous rappeler qu'en réalité tous les langages fonctionnels modernes fournissent des données mutables, que Haskell fournit même sans sacrifier la pureté (la monade ST).

44
jkff

Cet article affirme que les implémentations connues purement fonctionnelles de l'algorithme union-find ont toutes une complexité asymptotique pire que celle qu'elles publient, qui possède une interface purement fonctionnelle mais utilise des données mutables. intérieurement.

Le fait que d’autres réponses prétendent qu’il ne peut jamais y avoir de différence et que, par exemple, le seul "inconvénient" d’un code purement fonctionnel est qu’il peut être mis en parallèle, ce qui vous donne une idée de l’information/de l’objectivité de la communauté de la programmation fonctionnelle sur ces questions. .

MODIFIER:

Les commentaires ci-dessous indiquent qu'une discussion biaisée sur les avantages et les inconvénients de la programmation fonctionnelle pure peut ne pas provenir de la "communauté de programmation fonctionnelle". Bon point. Peut-être que les avocats que je vois sont juste, pour citer un commentaire, "analphabète".

Par exemple, je pense que ceci article de blog est écrit par quelqu'un qui pourrait être considéré comme étant représentatif de la communauté de la programmation fonctionnelle, et comme c'est une liste de "points pour l'évaluation paresseuse", ce serait un bon endroit pour mentionner tout inconvénient que pourrait avoir une programmation paresseuse et purement fonctionnelle. Une bonne place aurait été à la place du renvoi suivant (techniquement vrai, mais biaisé au point de ne pas être drôle):

Si une fonction stricte a une complexité O(f(n)) dans un langage strict, elle a une complexité O(f(n)) dans un langage paresseux pourquoi t'inquiéter? :)

35
Pascal Cuoq

Avec une limite supérieure fixe sur l'utilisation de la mémoire, il ne devrait y avoir aucune différence.

Esquisse de preuve: étant donné une limite supérieure fixe sur l'utilisation de la mémoire, il devrait être possible d'écrire une machine virtuelle qui exécute un jeu d'instructions impératives avec la même complexité asymptotique que si vous exécutiez réellement sur cette machine. En effet, vous pouvez gérer la mémoire mutable en tant que structure de données persistante, en donnant à O(log(n)) en lecture et en écriture, mais avec une limite supérieure fixe sur l'utilisation de la mémoire, vous pouvez avoir une quantité fixe de mémoire, entraînant leur décroissance en 0 (1). Ainsi, l'implémentation fonctionnelle peut être la version impérative exécutée dans l'implémentation fonctionnelle de la machine virtuelle et doit donc présenter la même complexité asymptotique.

4
Brian

Je suggérerais de lire sur performance de Haskell , puis de regarder jeu de tests performances pour les langages fonctionnels par rapport aux langages procéduraux/OO.

1
Kornel Kisielewicz