λ -calculus: Quelle est la plus efficace dans la représentation en mémoire des fonctions?


9

Je voudrais comparer les performances des structures de données codées par fonction (Church / Scott) par rapport aux structures de données classiquement codées (assembleur / C).

Mais avant de le faire, j'ai besoin de savoir à quel point la représentation des fonctions est / peut être efficace en mémoire. La fonction peut bien sûr être partiellement appliquée (aka fermeture).

Je m'intéresse à la fois à l'algorithme de codage actuel utilisé par les langages fonctionnels populaires (Haskell, ML) et au plus efficace possible.


Point bonus: existe-t-il un tel encodage qui mappe les entiers codés par fonction aux entiers natifs ( short, intetc. en C). Est-ce même possible?


J'apprécie l'efficacité basée sur la performance. En d'autres termes, plus le codage est efficace, moins il influence les performances de calcul avec des structures de données fonctionnelles.


Toutes mes tentatives Google ont échoué, je ne connais peut-être pas les bons mots clés.
Ford O.

Pouvez-vous modifier la question pour clarifier ce que vous entendez par «efficace»? Efficace pour quoi? Lorsque vous demandez une structure de données efficace, vous devez spécifier les opérations que vous souhaitez pouvoir effectuer sur la structure de données, car cela affecte le choix de la structure de données. Ou voulez-vous dire que l'encodage est aussi économe en espace que possible?
DW

1
C'est assez large. Il existe de nombreuses machines abstraites pour le calcul lambda qui visent à l'exécuter efficacement (voir par exemple SECD, CAM, Krivine's, STG). En plus de cela, vous devez considérer les données encodées Church / Scott, ce qui pose plus de problèmes. Par exemple, dans les listes codées Church, l'opération de queue doit être O (n) au lieu de O (1). Je pense avoir lu quelque part que l'existence d'un encodage pour les listes dans le système F avec des opérations de tête et de queue O (1) était toujours un problème ouvert.
chi

@DW Je parle de performances / frais généraux. Par exemple, avec un mappage de codage efficace sur la liste de l'église et la liste de Haskell devrait prendre le même temps.
Ford O.

Performance pour quelle (s) opération (s)? Que voulez-vous faire avec les fonctions? Voulez-vous évaluer ces fonctions sur une certaine valeur? Une fois, ou évaluer la même fonction sur plusieurs valeurs? Faire autre chose avec eux? Demandez-vous simplement comment compiler une fonction (écrite dans un langage fonctionnel) afin qu'elle puisse être exécutée le plus efficacement possible?
DW

Réponses:


11

Le truc, c'est qu'il n'y a vraiment pas beaucoup de latitude en termes d'encodage de fonction. Voici les principales options:

  • Réécriture des termes: vous stockez les fonctions sous forme d'arbres de syntaxe abstraits (ou un certain encodage de celles-ci. Lorsque vous appelez une fonction, vous parcourez manuellement l'arborescence de syntaxe pour remplacer ses paramètres par l'argument. C'est facile, mais terriblement inefficace en termes de temps et d'espace. .

  • Fermetures: vous avez un moyen de représenter une fonction, peut-être un arbre de syntaxe, plus probablement du code machine. Et dans ces fonctions, vous faites référence à vos arguments par référence d'une manière ou d'une autre. Il pourrait s'agir d'un décalage de pointeur, ce pourrait être un entier ou un index De Bruijn, ce pourrait être un nom. Ensuite, vous représentez une fonction comme une fermeture : la fonction "instructions" (arbre, code, etc.) couplée à une structure de données contenant toutes les variables libres de la fonction. Lorsqu'une fonction est réellement appliquée, elle sait en quelque sorte comment rechercher les variables libres dans sa structure de données, en utilisant des environnements, une arithmétique de pointeur, etc.

Je suis sûr qu'il existe d'autres options, mais ce sont les options de base, et je soupçonne que presque toutes les autres options seront une variante ou une optimisation de la structure de fermeture de base.

Ainsi, en termes de performances, les fermetures fonctionnent presque universellement mieux que la réécriture de termes. Des variations, quelle est la meilleure? Cela dépend fortement de votre langage et de votre architecture, mais je soupçonne que le "code machine avec une structure contenant des variables gratuites" est le plus efficace. Il a tout ce dont la fonction a besoin (instructions et valeurs) et rien de plus, et l'appel ne finit pas par faire des traversées à long terme.

Je m'intéresse à la fois à l'algorithme de codage actuel utilisé par les langages fonctionnels populaires (Haskell, ML)

Je ne suis pas un expert, mais je suis à 99% la plupart des saveurs ML utilisent une certaine variation des fermetures que je décris, mais avec quelques optimisations probables. Voir ceci pour une perspective (peut-être obsolète).

Haskell fait quelque chose d'un peu plus compliqué à cause de l'évaluation paresseuse: il utilise la réécriture de graphiques sans balises sans spin .

et aussi dans la plus efficace qui puisse être réalisée.

Qu'est-ce qui est le plus efficace? Aucune implémentation ne sera plus efficace sur toutes les entrées, vous obtenez donc des implémentations efficaces en moyenne, mais chacune excellera dans différents scénarios. Il n'y a donc pas de classement définitif des plus ou des moins efficaces.

Il n'y a pas de magie ici. Pour stocker une fonction, vous devez en quelque sorte stocker ses valeurs libres, sinon vous encodez moins d'informations que la fonction elle-même. Vous pouvez peut-être optimiser certaines des valeurs gratuites avec une évaluation partielle mais cela est risqué pour les performances, et vous devez faire attention à ce que cela s'arrête toujours.

Et, vous pouvez peut-être utiliser une sorte de compression ou un algorithme intelligent pour gagner en efficacité spatiale. Mais alors vous échangez du temps contre de l'espace, ou vous êtes dans une situation où vous avez optimisé pour certains cas et ralenti pour d'autres.

Vous pouvez optimiser pour le cas commun, mais ce que le cas le plus courant est peut changer la langue, domaine d'application, etc. Le type de code qui est rapide pour un jeu vidéo (nombre crissant, des boucles serrées avec une grande entrée) est probablement différente de celle ce qui est rapide pour un compilateur (traversées d'arborescence, listes de travail, etc.).

Point bonus: existe-t-il un tel encodage qui mappe les entiers codés par fonction aux entiers natifs (short, int, etc. en C). Est-ce même possible?

Non, ce n'est pas possible. Le problème est que le calcul lambda ne vous laisse pas introspecter les termes. Lorsqu'une fonction prend un argument du même type qu'un chiffre d'église, elle doit pouvoir l'appeler, sans examiner la définition exacte de ce chiffre. C'est la chose avec les encodages de l'Église: la seule chose que vous pouvez faire avec eux est de les appeler, et vous pouvez simuler tout ce qui est utile avec cela, mais pas sans coût.

Plus important encore, les entiers occupent tous les encodages binaires possibles. Donc, si les lambdas étaient représentés comme leurs entiers, vous n'auriez aucun moyen de représenter des lambdas non numérotés! Ou, vous introduisez un indicateur pour indiquer si un lambda est un chiffre ou non, mais alors toute efficacité que vous souhaitez est probablement passée par la fenêtre.

EDIT: Depuis que j'ai écrit ceci, j'ai découvert une troisième option pour implémenter des fonctions d'ordre supérieur: la défonctionnalisation . Ici, chaque appel de fonction se transforme en une grande switchinstruction, selon l'abstraction lambda donnée comme fonction. Le compromis ici est qu'il s'agit d'une transformation complète du programme: vous ne pouvez pas compiler des pièces séparément, puis les lier ensemble de cette façon, car vous devez disposer à l'avance de l'ensemble complet des abstractions lambda.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.