如何实现延续?
我正在编写一个用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) 。
有一些代码说明了这两个想法。
传统的方法是使用setjmp
和longjmp
,虽然有警告。
这是一个相当好的解释
持续时间基本上由在上下文切换点保存的堆栈状态和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数字,将会崩溃。