如何实现延续?

我正在编写一个用C语言编写的Scheme解释器。目前,它使用C运行时堆栈作为自己的堆栈,这在实现延续方面存在小问题。 我目前的解决scheme是将C堆栈手动复制到堆,然后在需要时将其复制回来。 除了不是标准的C,这个解决scheme并不理想。

在C中实现Scheme的延续最简单的方法是什么?

我记得阅读一篇可能对你有帮助的文章: 切尼在MTA上 🙂

schemeI的一些实现例如SISC知道在堆上分配它们的呼叫帧。

@ollie:如果所有的呼叫帧都在堆上,则不需要进行提升。 当然,在性能方面有一个折衷:提升的时间,与分配堆上所有帧所需的开销。 也许它应该是解释器中的可调参数。 😛

“ 一stream持续实施战略 ”(Clinger,Hartheimer和Ost的一篇文章)提供了一个很好的总结。 我build议特别关注Chez Scheme的实现。

堆栈复制并不那么复杂,并且有许多可以提高性能的方法。 使用堆分配的框架也是相当简单的,但是你做了一个权衡的创build开销“正常”的情况下,你不使用明确的延续。

如果您将input代码转换为延续传球风格(CPS),那么您可以完全消除堆栈。 但是,虽然CPS非常优雅,但它在前端添加了另一个处理步骤,并需要额外的优化来克服某些性能影响。

如果你从头开始,你真的应该看看延续传递风格(CPS)转换。

好消息来源包括“LISP的小块”和马克·菲利的计划在90分钟的介绍 。

Dybvig的论文似乎到目前为止还没有提到。 阅读是一种乐趣。 基于堆的模型是最容易实现的,但是基于堆的效率更高。 忽略基于string的模型。

R. Kent Dybvig。 “三项计划实施模式”。 ~dyb/papers/3imp.pdf

还请阅读ReadScheme.org上的实施文章。 http://library.readscheme.org/page8.html

摘要如下:

本文提出了三种scheme编程语言的实现模型。 第一个是在大多数Scheme实现中以某种forms使用的基于堆的模型; 第二个是基于堆栈的新模型,在执行大多数程序时比基于堆的模型有效得多; 第三个是用于Scheme的多处理器实现的新的基于string的模型。

基于堆的模型在堆中分配几个重要的数据结构,包括实际参数列表,绑定环境和调用框架。

只要有可能,基于堆栈的模型在堆栈上分配这些相同的结构。 这样可以减less堆分配,减less内存引用,缩短指令序列,减less垃圾回收,以及更有效率地使用内存。

基于string的模型在程序文本中正确地分配这些结构的版本,表示为一串符号。 在基于string的模型中,Scheme程序被转换成一种专门为支持Scheme而devise的FFP语言。 这种语言的程序直接由FFP机器执行,一台多处理器的string缩减计算机。

基于堆栈的模型是直接实用的模型; 它是作者Chez Scheme系统所使用的模型,是Scheme的高性能实现。 一旦机器被实现,基于string的模型将有助于将Scheme作为FFP机器上的FFP的高级替代品。

除了你迄今为止所得到的好的答案之外,我还是推荐Andrew Appel的“ 连续编译” 。 这是写得很好,虽然不直接处理C,它是编译器作家非常好的想法的来源。

鸡维基也有你会发现很有意思的页面,比如内部结构和编译过程 (CPS是用一个实际编译的例子来解释的)。

你可以看到的例子是: 鸡 (一个计划的实现,用C编写,支持延续); Paul Graham的On Lisp – 他创build了一个CPS变换器来实现Common Lisp中的延续子集; 和Weblocks – 一个基于延续的Web框架,它也在Common Lisp中实现了有限的延续forms。

持续不是问题:你可以使用CPS来实现那些具有常规高级function的人。 天真堆栈分配的问题是,尾部调用从来没有优化,这意味着你不能是计划。

将scheme的意大利面条栈映射到堆栈上的最佳方法是使用蹦床:基本上是额外的基础设施来处理非C类调用并退出程序。 见蹦床风格(ps) 。

有一些代码说明了这两个想法。

传统的方法是使用setjmplongjmp ,虽然有警告。

这是一个相当好的解释

持续时间基本上由在上下文切换点保存的堆栈状态和CPU寄存器组成。 至less在切换时不需要将整个堆栈复制到堆中,只能redirect堆栈指针。

继续使用光纤实现。 http://en.wikipedia.org/wiki/Fiber_%28computer_science%29 。 唯一需要仔细封装的是parameter passing和返回值。

在Windows中,光纤是使用CreateFiber / SwitchToFiber系列调用完成的。 在POSIX兼容系统中,可以使用makecontext / swapcontext来完成。

boost :: coroutine有一个可用作C ++协程的工作实现,可以作为实现的参考点。

改为使用显式堆栈。

帕特里克是正确的,唯一可以做到这一点的方法是在解释器中使用显式堆栈,并在需要转换为延续时将堆栈的相应段提升到堆中。

这与用支持它们的语言来支持闭包(闭包和连续有些相关)所需要的基本相同。

正如soegaard指出的那样,主要的参考依然是这个

这个想法是,一个延续是一个封闭的评估控制堆栈。 为了从使用call/cc创build延续的那一刻起继续进行评估,需要使用控制堆栈。

通常情况下,调用延续会导致很长的执行时间,并使用重复的堆栈来填充内存。 我写了这个愚蠢的代码来certificate,在mit-scheme中它使计划崩溃,

代码总和前1000个数字1+2+3+...+1000

 (call-with-current-continuation (lambda (break) ((lambda (s) (ss 1000 break)) (lambda (sn cc) (if (= 0 n) (cc 0) (+ n ;; non-tail-recursive, ;; the stack grows at each recursive call (call-with-current-continuation (lambda (__) (ss (- n 1) __))))))))) 

如果从1000转换到100 000,代码将花费2秒钟,如果增加input数字,将会崩溃。