什么是蹦床功能?

发布于 2024-07-06 15:18:38 字数 192 浏览 9 评论 0原文

在最近的工作讨论中,有人提到了蹦床功能。

我已阅读维基百科上的描述。 给出功能的一般概念就足够了,但我想要更具体的东西。

您有一段简单的代码片段可以说明蹦床吗?

During recent discussions at work, someone referred to a trampoline function.

I have read the description at Wikipedia. It is enough to give a general idea of the functionality, but I would like something a bit more concrete.

Do you have a simple snippet of code that would illustrate a trampoline?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

久夏青 2024-07-13 15:18:38

还有 LISP 意义上的“蹦床”,如 Wikipedia 上所述:

在一些 LISP 实现中使用,
蹦床是一个迭代循环
调用 thunk 返回函数。 A
单个蹦床就足够了
表达a的所有控制转移
程序; 这样表达的程序是
蹦床式或“蹦床式”;
将程序转换为蹦床程序
风格是蹦床。 蹦床
可以使用函数来实现
尾递归函数调用
面向堆栈的语言

假设我们正在使用 Javascript,并希望以连续传递风格编写朴素的斐波那契函数。 我们这样做的原因并不相关——例如将Scheme移植到JS,或者使用我们必须使用的CPS来调用服务器端函数。

因此,第一次尝试是,

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

但是,在 Firefox 中使用 n = 25 运行此命令会出现错误“递归过多!”。 现在这正是蹦床解决的问题(Javascript 中缺少尾部调用优化)。 让我们返回一条指令(thunk)来调用该函数,而不是对函数进行(递归)调用,以便在循环中进行解释。

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

There is also the LISP sense of 'trampoline' as described on Wikipedia:

Used in some LISP implementations, a
trampoline is a loop that iteratively
invokes thunk-returning functions. A
single trampoline is sufficient to
express all control transfers of a
program; a program so expressed is
trampolined or in "trampolined style";
converting a program to trampolined
style is trampolining. Trampolined
functions can be used to implement
tail recursive function calls in
stack-oriented languages

Let us say we are using Javascript and want to write the naive Fibonacci function in continuation-passing-style. The reason we would do this is not relevant - to port Scheme to JS for instance, or to play with CPS which we have to use anyway to call server-side functions.

So, the first attempt is

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

But, running this with n = 25 in Firefox gives an error 'Too much recursion!'. Now this is exactly the problem (missing tail-call optimization in Javascript) that trampolining solves. Instead of making a (recursive) call to a function, let us return an instruction (thunk) to call that function, to be interpreted in a loop.

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]});
}
酒解孤独 2024-07-13 15:18:38

让我添加一些用不同语言使用蹦床实现的阶乘函数的示例:

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(不幸的是没有大数字实现):

#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, ¶ms};

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

  return 0;
}

Let me add few examples for factorial function implemented with trampolines, in different languages:

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 (unlucky without big numbers implementation):

#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, ¶ms};

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

  return 0;
}
梦冥 2024-07-13 15:18:38

我给你举一个我在在线游戏反作弊补丁中使用的例子。

我需要能够扫描游戏正在加载的所有文件以进行修改。 因此,我发现执行此操作的最可靠方法是对 CreateFileA 使用蹦床。 因此,当游戏启动时,我会使用 GetProcAddress 找到 CreateFileA 的地址,然后我会修改函数的前几个字节并插入会跳转到我自己的“trampoline”函数的汇编代码,我会在其中执行一些操作,并且然后我会跳回到 CreateFile 中 jmp 代码之后的下一个位置。 要可靠地做到这一点比这要困难一些,但基本概念就是挂钩一个函数,强制它重定向到另一个函数,然后跳回原始函数。

编辑:微软有一个针对此类事物的框架,您可以查看。 称为绕道

I'll give you an example that I used in an anti-cheat patch for an online game.

I needed to be able to scan all files that were being loaded by the game for modification. So the most robust way I found to do this was to use a trampoline for CreateFileA. So when the game was launched I would find the address for CreateFileA using GetProcAddress, then I would modify the first few bytes of the function and insert assembly code that would jump to my own "trampoline" function, where I would do some things, and then I would jump back to the next location in CreateFile after my jmp code. To be able to do it reliably is a little trickier than that, but the basic concept is just to hook one function, force it to redirect to another function, and then jump back to the original function.

Edit: Microsoft has a framework for this type of thing that you can look at. Called Detours

猥琐帝 2024-07-13 15:18:38

以下是嵌套函数的示例:

#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);
}

compar 不能是外部函数,因为它使用 nbytes,而该函数仅在 sort_bytes 调用期间存在。 在某些架构上,一个小的存根函数(trampoline)是在运行时生成的,并且包含当前调用sort_bytes的堆栈位置。 调用时,它会跳转到 compar 代码,并传递该地址。

在像 PowerPC 这样的架构上不需要这种混乱,其中 ABI 指定函数指针实际上是一个“胖指针”,一个包含指向可执行代码的指针和另一个指向数据的指针的结构。 然而,在 x86 上,函数指针只是一个指针。

Here's an example of nested functions:

#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);
}

compar can't be an external function, because it uses nbytes, which only exists during the sort_bytes call. On some architectures, a small stub function -- the trampoline -- is generated at runtime, and contains the stack location of the current invocation of sort_bytes. When called, it jumps to the compar code, passing that address.

This mess isn't required on architectures like PowerPC, where the ABI specifies that a function pointer is actually a "fat pointer", a structure containing both a pointer to the executable code and another pointer to data. However, on x86, a function pointer is just a pointer.

深爱不及久伴 2024-07-13 15:18:38

我目前正在尝试为Scheme解释器实现尾部调用优化的方法,因此目前我正在尝试弄清楚蹦床对我来说是否可行。

据我了解,它基本上只是由蹦床函数执行的一系列函数调用。 每个函数都称为 thunk,并返回计算中的下一步,直到程序终止(空延续)。

这是我为提高对蹦床的理解而编写的第一段代码:

#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);
}

结果:

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

I am currently experimenting with ways to implement tail call optimization for a Scheme interpreter, and so at the moment I am trying to figure out whether the trampoline would be feasible for me.

As I understand it, it is basically just a series of function calls performed by a trampoline function. Each function is called a thunk and returns the next step in the computation until the program terminates (empty continuation).

Here is the first piece of code that I wrote to improve my understanding of the trampoline:

#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);
}

results in:

meincompi $ ./trampoline 
*boing* thunk 1
*boing* thunk 2
*boing* last thunk
got off the trampoline - happy happy joy joy !
随波逐流 2024-07-13 15:18:38

现在 C# 具有 本地函数保龄球游戏编码型可以用蹦床优雅地解决:

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));
    }
}

该方法Game.RollMany 会通过多次掷骰来调用:如果没有备用或罢工,通常为 20 次掷骰。

第一行立即调用trampoline函数:return Trampoline(1, 0, rs.ToList());。 该局部函数递归遍历 rolls 数组。 本地函数(trampoline)允许遍历从两个附加值开始:从 frame 1 和 rsf (到目前为止的结果)0 开始。

在本地函数中是三元运算符,处理五种情况:

  • 游戏在第 11 帧结束:返回目前为止的结果
  • 如果没有更多的掷骰,则游戏结束:返回目前为止的结果
  • Strike:计算帧分数并继续遍历
  • Spare:计算帧分数并继续遍历
  • 正常分数:计算帧分数并继续遍历

继续遍历是通过再次调用蹦床来完成的,但现在使用更新的值。

有关更多信息,请搜索:“尾递归累加器”。 请记住,编译器不会优化尾递归。 因此,尽管这个解决方案可能很优雅,但它可能不会是禁食的。

Now that C# has Local Functions, the Bowling Game coding kata can be elegantly solved with a trampoline:

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));
    }
}

The method Game.RollMany is called with a number of rolls: typically 20 rolls if there are no spares or strikes.

The first line immediately calls the trampoline function: return Trampoline(1, 0, rs.ToList());. This local function recursively traverses the rolls array. The local function (the trampoline) allows the traversal to start with two additional values: start with frame 1 and the rsf (result so far) 0.

Within the local function there is ternary operator that handles five cases:

  • Game ends at frame 11: return the result so far
  • Game ends if there are no more rolls: return the result so far
  • Strike: calculate the frame score and continue traversal
  • Spare: calculate the frame score and continue traversal
  • Normal score: calculate the frame score and continue traversal

Continuing the traversal is done by calling the trampoline again, but now with updated values.

For more information, search for: "tail recursion accumulator". Keep in mind that the compiler does not optimize tail recursion. So as elegant as this solution may be, it will likely not be the fasted.

鸵鸟症 2024-07-13 15:18:38

对于 C,蹦床将是一个函数指针:

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");

编辑:编译器将隐式生成更多深奥的蹦床。 其中一种用途是跳转表。 (尽管您开始尝试生成复杂的代码越往下,显然有更复杂的代码。)

For C, a trampoline would be a function pointer:

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");

Edit: More esoteric trampolines would be implicitly generated by the compiler. One such use would be a jump table. (Although there are clearly more complicated ones the farther down you start attempting to generate complicated code.)

万水千山粽是情ミ 2024-07-13 15:18:38
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();
}
// ...
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();
}
// ...
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文