Was ist eine Trampolinfunktion?

93

Während der letzten Diskussionen bei der Arbeit bezog sich jemand auf eine Trampolinfunktion.

Ich habe die Beschreibung bei Wikipedia gelesen . Es reicht aus, einen allgemeinen Überblick über die Funktionalität zu geben, aber ich möchte etwas Konkreteres.

Haben Sie einen einfachen Codeausschnitt, der ein Trampolin illustriert?

Benoit
quelle
2
In der Microsoft-Welt werden Trampoline normalerweise als "Thunks" bezeichnet. [Hier ist eine Seite] [1] aus Andrei Alexandrescus "Modern C ++ Design" ---- [1]: books.google.com/…
Michael Burr
1
Verwandte
Bobobobo
Es ist grundlegend die verallgemeinerte Form einiger Funktionen, die Sie mit setjmp / lomgjmp implementieren können, nämlich um einen Stapelüberlauf zu vermeiden.
Ingo
12
Warum sollte jemand einen Stackoverflow vermeiden wollen?
Nikole

Antworten:

72

Es gibt auch den LISP-Sinn für "Trampolin", wie auf Wikipedia beschrieben:

In einigen LISP-Implementierungen wird ein Trampolin als Schleife verwendet, die iterativ Thunk-Return-Funktionen aufruft. Ein einziges Trampolin reicht aus, um alle Kontrollübertragungen eines Programms auszudrücken. ein so ausgedrücktes Programm ist trampoliniert oder "trampoliniert"; Das Konvertieren eines Programms in einen trampolinierten Stil ist Trampolin. Trampolinfunktionen können verwendet werden, um rekursive Funktionsaufrufe in stapelorientierten Sprachen zu implementieren

Nehmen wir an, wir verwenden Javascript und möchten die naive Fibonacci-Funktion im Continuation-Passing-Stil schreiben. Der Grund, warum wir dies tun würden, ist nicht relevant - zum Beispiel, um Scheme auf JS zu portieren oder um mit CPS zu spielen, das wir sowieso verwenden müssen, um serverseitige Funktionen aufzurufen.

Der erste Versuch ist also

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

Wenn Sie dies jedoch n = 25in Firefox ausführen, wird der Fehler "Zu viel Rekursion!" Angezeigt. Dies ist genau das Problem (fehlende Tail-Call-Optimierung in Javascript), das Trampolin löst. Anstatt eine Funktion (rekursiv) aufzurufen, geben wir returneine Anweisung (thunk) an, um diese Funktion aufzurufen und in einer Schleife zu interpretieren.

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]});
}
Winkel
quelle
39

Lassen Sie mich einige Beispiele für die mit Trampolinen implementierte Fakultätsfunktion in verschiedenen Sprachen hinzufügen:

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<T> 
{
    public T get() { return null; }
    public Trampoline<T>  run() { return null; }

    T execute() {
        Trampoline<T>  trampoline = this;

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

        return trampoline.get();
    }
}

public class Factorial
{
    public static Trampoline<BigInteger> factorial(final int n, final BigInteger product)
    {
        if(n <= 1) {
            return new Trampoline<BigInteger>() { public BigInteger get() { return product; } };
        }   
        else {
            return new Trampoline<BigInteger>() { 
                public Trampoline<BigInteger> 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 (Pech ohne Implementierung großer Zahlen):

#include <stdio.h>

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, &params};

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

  return 0;
}
Piotr Kukielka
quelle
Ihre Erklärung, insbesondere das C-Beispiel, sowie die Antwort von Ephemient über verschachtelte Funktionen haben mich schließlich dazu gebracht, Trampoline zu verstehen. Eine Art Hilfsfunktion, mit der der Status ähnlich wie bei einem Abschluss aktualisiert werden kann.
Byte
Scala-Code sollte korrigiert werden if (n < 2) Done(product), SO erlaubte mir nicht, 1 Symbol zu bearbeiten ...
Max
21

Ich gebe Ihnen ein Beispiel, das ich in einem Anti-Cheat-Patch für ein Online-Spiel verwendet habe.

Ich musste in der Lage sein, alle Dateien, die vom Spiel geladen wurden, auf Änderungen zu scannen. Der robusteste Weg, dies zu tun, war die Verwendung eines Trampolins für CreateFileA. Wenn das Spiel gestartet wurde, fand ich die Adresse für CreateFileA mithilfe von GetProcAddress, änderte dann die ersten Bytes der Funktion und fügte Assembler-Code ein, der zu meiner eigenen "Trampolin" -Funktion springen würde, wo ich einige Dinge tun würde, und dann würde ich nach meinem jmp-Code zum nächsten Speicherort in CreateFile zurückspringen. Es ist etwas schwieriger, dies zuverlässig zu tun, aber das Grundkonzept besteht darin, nur eine Funktion zu verknüpfen, sie zu zwingen, zu einer anderen Funktion umzuleiten, und dann zur ursprünglichen Funktion zurückzukehren.

Bearbeiten: Microsoft hat ein Framework für diese Art von Dingen, die Sie betrachten können. genannt Detours

Gerald
quelle
8

Ich experimentiere derzeit mit Möglichkeiten zur Implementierung der Tail-Call-Optimierung für einen Scheme-Interpreter. Daher versuche ich im Moment herauszufinden, ob das Trampolin für mich machbar wäre.

Soweit ich weiß, handelt es sich im Grunde genommen nur um eine Reihe von Funktionsaufrufen, die von einer Trampolinfunktion ausgeführt werden. Jede Funktion wird als Thunk bezeichnet und gibt den nächsten Schritt in der Berechnung zurück, bis das Programm beendet wird (leere Fortsetzung).

Hier ist der erste Code, den ich geschrieben habe, um mein Verständnis des Trampolins zu verbessern:

#include <stdio.h>

typedef void *(*CONTINUATION)(int);

void trampoline(CONTINUATION cont)
{
  int counter = 0;
  CONTINUATION currentCont = cont;
  while (currentCont != NULL) {
    currentCont = (CONTINUATION) currentCont(counter);
    counter++;
  }
  printf("got off the trampoline - happy happy joy joy !\n");
}

void *thunk3(int param)
{
  printf("*boing* last 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);
}

Ergebnisse in:

meincompi $ ./trampoline 
*boing* thunk 1
*boing* thunk 2
*boing* last thunk
got off the trampoline - happy happy joy joy !
Boxofrats
quelle
7

Hier ist ein Beispiel für verschachtelte Funktionen:

#include <stdlib.h>
#include <string.h>
/* sort an array, starting at address `base`,
 * containing `nmemb` members, separated by `size`,
 * comparing on the first `nbytes` only. */
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);
}

comparkann keine externe Funktion sein, da sie verwendet nbytes, die nur während des sort_bytesAufrufs vorhanden ist. Bei einigen Architekturen wird zur Laufzeit eine kleine Stub-Funktion - das Trampolin - generiert, die den Stapelspeicherort des aktuellen Aufrufs von enthält sort_bytes. Beim Aufruf springt es zum comparCode und übergibt diese Adresse.

Dieses Durcheinander ist bei Architekturen wie PowerPC nicht erforderlich, bei denen der ABI angibt, dass ein Funktionszeiger tatsächlich ein "fetter Zeiger" ist, eine Struktur, die sowohl einen Zeiger auf den ausführbaren Code als auch einen weiteren Zeiger auf Daten enthält. Unter x86 ist ein Funktionszeiger jedoch nur ein Zeiger.

kurzlebig
quelle
0

Für C wäre ein Trampolin ein Funktionszeiger:

size_t (*trampoline_example)(const char *, const char *);
trampoline_example= strcspn;
size_t result_1= trampoline_example("xyzbxz", "abc");

trampoline_example= strspn;
size_t result_2= trampoline_example("xyzbxz", "abc");

Bearbeiten: Esoterischere Trampoline würden implizit vom Compiler generiert. Eine solche Verwendung wäre ein Sprungtisch. (Obwohl es deutlich kompliziertere gibt, versuchen Sie weiter unten, komplizierten Code zu generieren.)

MSN
quelle
0

Jetzt, da C # über lokale Funktionen verfügt , kann die Bowling Game-Codierungskata elegant mit einem Trampolin gelöst werden:

using System.Collections.Generic;
using System.Linq;

class Game
{
    internal static int RollMany(params int[] rs) 
    {
        return Trampoline(1, 0, rs.ToList());

        int Trampoline(int frame, int rsf, IEnumerable<int> rs) =>
              frame == 11             ? rsf
            : rs.Count() == 0         ? rsf
            : rs.First() == 10        ? Trampoline(frame + 1, rsf + rs.Take(3).Sum(), rs.Skip(1))
            : rs.Take(2).Sum() == 10  ? Trampoline(frame + 1, rsf + rs.Take(3).Sum(), rs.Skip(2))
            :                           Trampoline(frame + 1, rsf + rs.Take(2).Sum(), rs.Skip(2));
    }
}

Die Methode Game.RollManywird mit einer Anzahl von Rollen aufgerufen: normalerweise 20 Rollen, wenn keine Ersatzteile oder Schläge vorhanden sind.

Die erste Zeile ruft sofort die Trampolinfunktion auf : return Trampoline(1, 0, rs.ToList());. Diese lokale Funktion durchläuft rekursiv das Rollenarray. Die lokale Funktion (das Trampolin) ermöglicht es der Durchquerung, mit zwei zusätzlichen Werten zu beginnen: Start mit frame1 und rsf(bisheriges Ergebnis) 0.

Innerhalb der lokalen Funktion gibt es einen ternären Operator, der fünf Fälle behandelt:

  • Das Spiel endet bei Bild 11: Geben Sie das bisherige Ergebnis zurück
  • Das Spiel endet, wenn keine Würfe mehr vorhanden sind: Geben Sie das bisherige Ergebnis zurück
  • Strike: Berechnen Sie die Frame-Punktzahl und fahren Sie mit dem Durchlaufen fort
  • Ersatz: Berechnen Sie die Frame-Punktzahl und fahren Sie mit dem Durchlaufen fort
  • Normale Punktzahl: Berechnen Sie die Frame-Punktzahl und fahren Sie mit dem Durchlaufen fort

Die Fortsetzung des Durchlaufs erfolgt durch erneutes Aufrufen des Trampolins, jetzt jedoch mit aktualisierten Werten.

Weitere Informationen finden Sie unter: " Schwanzrekursionsakkumulator ". Beachten Sie, dass der Compiler die Schwanzrekursion nicht optimiert. So elegant diese Lösung auch sein mag, es wird wahrscheinlich nicht das Fasten sein.

RandomProgrammer
quelle
-2
typedef void* (*state_type)(void);
void* state1();
void* state2();
void* state1() {
  return state2;
}
void* state2() {
  return state1;
}
// ...
state_type state = state1;
while (1) {
  state = state();
}
// ...
thenry
quelle
3
Können Sie Kommentare oder Erklärungen hinzufügen, warum dies ein Trampolin ist?
Prasun