119 votes

Qu'est-ce qu'une fonction trampoline ?

Lors de récentes discussions au travail, quelqu'un a fait référence à une fonction trampoline.

J'ai lu la description sur Wikipédia. Cela suffit pour se faire une idée générale de la fonctionnalité, mais j'aimerais quelque chose de plus concret.

Auriez-vous un petit extrait de code simple qui pourrait illustrer une trampoline?

2 votes

Dans le monde Microsoft, les trampolines sont généralement appelés "thunks". [Voici une page](http://books.google.com/books?id=aJ1av7UFBPwC&pg=PA280&lpg=PA280&dq=thunk+trampoline&source=web&ots=YQcG0nOf7Z&sig=PZA-Pbx19C9Mnw\_1myezK4x3QWQ&hl=en&sa=X&oi=book\_result&resnum=4&ct=result) tirée du livre "Modern C++ Design" d'Andrei Alexandrescu.

1 votes

0 votes

C'est essentiellement la forme généralisée de certaines fonctionnalités que vous pourriez implémenter avec setjmp/longjmp, à savoir éviter le dépassement de pile.

83voto

Il existe également le sens de 'trampoline' en LISP tel que décrit sur Wikipedia :

Utilisé dans certaines implémentations de LISP, un trampoline est une boucle qui invoque de manière itérative des fonctions renvoyant un thunk. Un seul trampoline est suffisant pour exprimer tous les transferts de contrôle d'un programme ; un programme ainsi exprimé est trampoliné ou en "style trampoliné" ; convertir un programme en style trampoliné est du trampolinage. Les fonctions trampolinées peuvent être utilisées pour implémenter des appels de fonction récursive terminale dans des langages orientés sur la pile

Disons que nous utilisons Javascript et que nous voulons écrire la fonction Fibonacci naïve en style de passage de continuation. La raison pour laquelle nous ferions cela n'est pas pertinente - pour porter Scheme vers JS par exemple, ou pour jouer avec le CPS que nous devons de toute façon utiliser pour appeler des fonctions côté serveur.

Ainsi, la première tentative est la suivante :

function fibcps(n, c) {
    if (n <= 1) {
        c(n);
    } else {
        fibcps(n - 1, function (x) {
            fibcps(n - 2, function (y) {
                c(x + y)
            })
        });
    }
}

Cependant, en exécutant ceci avec n = 25 dans Firefox, une erreur 'Trop de récursion !' se produit. C'est précisément le problème (optimisation des appels récursifs terminaux manquante en Javascript) que la trampolisation résout. Au lieu de faire un appel (récursif) à une fonction, retournons une instruction (thunk) pour appeler cette fonction, à interpréter dans une boucle.

function fibt(n, c) {
    function trampoline(x) {
        while (x && x.func) {
            x = x.func.apply(null, x.args);
        }
    }

    function fibtramp(n, c) {
        if (n <= 1) {
            return {func: c, args: [n]};
        } else {
            return {
                func: fibtramp,
                args: [n - 1,
                    function (x) {
                        return {
                            func: fibtramp,
                            args: [n - 2, function (y) {
                                return {func: c, args: [x + y]}
                            }]
                        }
                    }
                ]
            }
        }
    }

    trampoline({func: fibtramp, args: [n, c]});
}

48voto

Piotr Kukielka Points 1465

Laissez-moi ajouter quelques exemples pour la fonction factorielle implémentée avec des trampolines, dans différentes langues :

Scala :

sealed trait Bounce[A]
case class Done[A](result: A) extends Bounce[A]
case class Call[A](thunk: () => Bounce[A]) extends Bounce[A]

def trampoline[A](bounce: Bounce[A]): A = bounce match {
  case Call(thunk) => trampoline(thunk())
  case Done(x) => x
}

def factorial(n: Int, product: BigInt): Bounce[BigInt] = {
    if (n <= 2) Done(product)
    else Call(() => factorial(n - 1, n * product))
}

object Factorial extends Application {
    println(trampoline(factorial(100000, 1)))
}

Java :

import java.math.BigInteger;

class Trampoline 
{
    public T get() { return null; }
    public Trampoline  run() { return null; }

    T execute() {
        Trampoline  trampoline = this;

        while (trampoline.get() == null) {
            trampoline = trampoline.run();
        }

        return trampoline.get();
    }
}

public class Factorial
{
    public static Trampoline factorial(final int n, final BigInteger product)
    {
        if(n <= 1) {
            return new Trampoline() { public BigInteger get() { return product; } };
        }   
        else {
            return new Trampoline() { 
                public Trampoline run() { 
                    return factorial(n - 1, product.multiply(BigInteger.valueOf(n)));
                } 
            };
        }
    }

    public static void main( String [ ] args )
    {
        System.out.println(factorial(100000, BigInteger.ONE).execute());
    }
}

C (malheureusement sans implémentation de grands nombres) :

#include 

typedef struct _trampoline_data {
  void(*callback)(struct _trampoline_data*);
  void* parameters;
} trampoline_data;

void trampoline(trampoline_data* data) {
  while(data->callback != NULL)
    data->callback(data);
}

//-----------------------------------------

typedef struct _factorialParameters {
  int n;
  int product;
} factorialParameters;

void factorial(trampoline_data* data) {
  factorialParameters* parameters = (factorialParameters*) data->parameters;

  if (parameters->n <= 1) {
    data->callback = NULL;
  }
  else {
    parameters->product *= parameters->n;
    parameters->n--;
  }
}

int main() {
  factorialParameters params = {5, 1};
  trampoline_data t = {&factorial, ¶ms};

  trampoline(&t);
  printf("\n%d\n", params.product);

  return 0;
}

1 votes

Votre explication, en particulier l'exemple C, ainsi que la réponse d'ephemient ci-dessous concernant les fonctions imbriquées, m'ont enfin fait comprendre les trampolines. Une sorte de fonction auxiliaire qui peut être utilisée pour mettre à jour l'état de la même manière qu'une fermeture.

0 votes

Le code Scala doit être corrigé en if (n < 2) Done(product), SO ne m'a pas autorisé à éditer 1 symbole...

23voto

Gerald Points 13865

Je vais vous donner un exemple que j'ai utilisé dans un correctif anti-triche pour un jeu en ligne.

J'avais besoin de pouvoir analyser tous les fichiers qui étaient chargés par le jeu pour modification. La manière la plus robuste que j'ai trouvée pour le faire était d'utiliser un trampoline pour CreateFileA. Ainsi, lorsque le jeu était lancé, je devais trouver l'adresse de CreateFileA en utilisant GetProcAddress, puis je modifiais les premiers octets de la fonction et j'insérais du code assembleur qui sauterait vers ma propre fonction de "trampoline", où je ferais certaines choses, puis je sauterais de nouveau à l'emplacement suivant dans CreateFile après mon code jmp. Pour pouvoir le faire de manière fiable, c'est un peu plus délicat que cela, mais le concept de base est simplement de crocheter une fonction, de la forcer à rediriger vers une autre fonction, puis de sauter de nouveau vers la fonction originale.

Edit: Microsoft a un framework pour ce type de chose que vous pouvez consulter. Appelé Detours

9voto

boxofrats Points 692

Actuellement, j'expérimente des façons de mettre en œuvre l'optimisation des appels de queue pour un interprète Scheme, et donc en ce moment j'essaie de déterminer si le trampoline serait faisable pour moi.

D'après ce que je comprends, il s'agit essentiellement d'une série d'appels de fonction effectués par une fonction trampoline. Chaque fonction est appelée un thunk et renvoie l'étape suivante dans le calcul jusqu'à ce que le programme se termine (continuation vide).

Voici le premier morceau de code que j'ai écrit pour améliorer ma compréhension du trampoline :

#include 

typedef void *(*CONTINUATION)(int);

void trampoline(CONTINUATION cont)
{
  int counter = 0;
  CONTINUATION currentCont = cont;
  while (currentCont != NULL) {
    currentCont = (CONTINUATION) currentCont(counter);
    counter++;
  }
  printf("sorti du trampoline - joyeux joyeux bonheur !\n");
}

void *thunk3(int param)
{
  printf("*boing* dernier thunk\n");
  return NULL;
}

void *thunk2(int param)
{
  printf("*boing* thunk 2\n");
  return thunk3;
}

void *thunk1(int param)
{
  printf("*boing* thunk 1\n");
  return thunk2;
}

int main(int argc, char **argv)
{
  trampoline(thunk1);
}

résultat :

meincompi $ ./trampoline 
*boing* thunk 1
*boing* thunk 2
*boing* dernier thunk
sorti du trampoline - joyeux joyeux bonheur !

9voto

ephemient Points 87003

Voici un exemple de fonctions imbriquées :

#include <stdlib.h>
#include <string.h>
/ * trier un tableau, en commençant à l'adresse `base`,
 * contenant `nmemb` membres, séparés par `size`,
 * en comparant uniquement les premiers `nbytes`. * /
void sort_bytes(void *base, size_t nmemb, size_t size, size_t nbytes) {
    int compar(const void *a, const void *b) {
        return memcmp(a, b, nbytes);
    }
    qsort(base, nmemb, size, compar);
}

compar ne peut pas être une fonction externe, car elle utilise nbytes, qui n'existe que pendant l'appel à sort_bytes. Sur certaines architectures, une petite fonction trampoline est générée à l'exécution et contient l'emplacement de pile de l'invocation actuelle de sort_bytes. Lorsqu'elle est appelée, elle saute au code de compar, en passant cette adresse.

Ce bazar n'est pas nécessaire sur des architectures comme PowerPC, où l'ABI spécifie qu'un pointeur de fonction est en réalité un "pointeur gras", une structure contenant à la fois un pointeur vers le code exécutable et un autre pointeur vers les données. Cependant, sur x86, un pointeur de fonction n'est qu'un simple pointeur.

0 votes

J'ai voulu explorer cela et j'ai remarqué que cela ne se compilait pas avec un compilateur C standard. gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html Cette page contient des informations pertinentes. Les fonctions imbriquées sont implémentées dans GCC en tant qu'extension. "GCC implémente la prise de l'adresse d'une fonction imbriquée en utilisant une technique appelée trampolines. Cette technique a été décrite dans Closures lexicales pour C++ (Thomas M. Breuel, Actes de la conférence C++ USENIX, du 17 au 21 octobre 1988)." Le voici dans godbolt : godbolt.org/z/W9W9PdMPv

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X