用Chez方案重建球拍编译器

2020-11-27 03:44:59

Racket标榜了可编程语言的名称。它以可扩展性为核心,通过鼓励开发人员实现自己的DSL来解决当前的问题,将元编程提升到一个新的水平。

遵循相同的原则,其开发团队通过堆叠DSL层来实现其许多组件,从而攻击了编写编译器的复杂性。

另一方面,该项目有许多用C编写的遗留组件成为开发瓶颈,因此在2017年,Matthew Flatt在Racket Developers小组中宣布:

Chez是Scheme实施,由Cisco在2016年开源。Chez的性能在其他计划中无与伦比,并且在生产中使用已久。

要了解有关此工作的更多信息,我们联系了Gustavo Massaccesi和Matthew Flatt,他们是现在所谓的Racket CS项目的一部分。在这次采访中,他们解释了该项目的背景和细节。

我们是Matthew Flatt的工作和球拍事业的忠实拥护者。为了进一步阅读,我们建议阅读Flatt的文章“在Racket中创建语言”,对38位Racket程序员进行访谈的书以及Flatt所著的《 Beautiful Racket》一书。

加入“非Monad教程电报”小组或频道,讨论编程,计算机科学和论文。到时候那里见!

如果您正在寻找优秀的工程师,请给我发送电子邮件至[email protected],或者也可以通过Twitter @federicocarrone与我联系。

让我们区分“用语言拍”和“用项目拍”。

Racket语言是一种通用的,类似于Scheme的语言,具有一组特别丰富的构造来扩展该语言-即使按照Scheme标准。 Racket包括对编写快速脏宏的支持,但它也支持带有良好错误检查的漂亮宏,可以避免在扩展代码中创建令人惊讶的错误。宏和模块的紧密集成,运行时代码与编译时代码之间的强制相分离以及用于选择表面语法的`#lang`机制,都使Racket与其他Lisp变体区分开来。

甚至主要语言Racket都是用一种更简单的语言编写的,也就是用一种甚至更简单的语言编写的。语言之塔使开发更加容易。您可以深入了解所有内部语言,或者只是忽略所有内部语言而获得不错的高级语言。

Racket的运行时结构不那么突出,但在实践中对于构建语言抽象并将它们组成大型系统也很重要:带有连续标记的一流控制,用于简单可靠的任务终止的管理员,基于可达性的内存记帐,消息基于位置的并行性,以及事件驱动程序的并发ML样式构造。这些构造中的许多构造都需要在运行时系统的较低级别上提供支持,但随后可以将它们用于构建各种可以很好地啮合的语言和库。

Racket项目将研究,生产和教育工作综合起来,以实现总体语言建设目标。 “建立一种可编程的编程语言”的理念沿这些方向服务,从构建对学生友好的学习环境到应用中特定领域的语言,再到推动语言设计和实现的前沿。

例如,当您安装Racket时,它附带20或30种其他语言。 (我不确定是否有人把它们都数了。)

有一些为学生设计的“学生”语言。它们的功能较弱,但具有更多的编译时间检查功能,可以检测初学者中的常见错误。它们具有不同的级别,因此一旦您掌握了一个级别,就可以使用包含更多功能的下一个级别。

另一种语言是Typed Racket,它向Racket表达式添加类型,因此除非类型检查,否则它拒绝编译。并且它还使用类型信息来优化代码,因此编译速度较慢,但​​是生成的代码可以更快。

有一些语言可以在R5RS和R6RS以及许多SRFI中实现Scheme版本。而且,您可以安装一个在R7RS-small中添加版本的软件包。

此外,还有更多语法不同的语言,例如Algol 60的完整实现。

所有这些语言都共享同一个后端,您可以从发行版中包含的任何其他语言,可以打包下载的其他语言或创建的语言中调用用一种语言编写的库。

Racket最初是作为Scheme实施而来的,我们仍称其为“ Scheme”。即使不符合Scheme标准,也显然是从Scheme派生的。有很多特定的区别,例如,cons总是在Racket中创建一个不变的对,但主要区别是哲学:Scheme是一种很小的语言,足以让您表达很多东西。球拍本来是一门大语言,虽然它为您提供了可以表达很多内容的相同(甚至更多)核心片段,但它也使许多事情得以完成,从而实现了更多协作的库和语言。

Chez Scheme是最古老的Scheme实施方案之一,它的演变通过R6RS通知了Scheme标准的许多部分。 (相比之下,Racket对Scheme标准的影响仅限于R6RS库设计的各个方面。)Chez Scheme是一种相对较小的语言,但与Scheme的所有实例化一样,实现所提供的功能远远超出标准规定。

切斯计划(Chez Scheme)最著名的成就就是表现。它一直是性能最佳的Scheme实施方案之一。即使在2020年,它的对象标记和分配机制,混合堆栈的延续实现以及其编译器结构仍保持最先进的状态。

Chez Scheme在其大部分时间内都是专有的,封闭源代码的实施,但于2016年中期成为开源。碰巧的是,我们开始考虑在2017年初左右重新实施球拍。

Racket BC(“ Chez之前”)实现的最大弱点是其后端编译器结构,低效的内部调用约定(过度适应C)以及对一流延续的糟糕实现。这些正是Chez Scheme的优势。此外,Racket的评估模型始终与Chez Scheme紧密结合,例如强调交互式评估和编译。

很明显,Chez Scheme缺少Racket所需的重要功能,例如对连续标记的支持和基于可达性的内存记帐。但是,与旧版Racket的实施方式相比,Chez方案的设计和实施的高质量使适应Chez方案的吸引力远胜于对Racket的旧实施例的改进。

为什么用Chez Scheme重新实现以减少C部分,而不是在Racket中实现C语言?

通常,我们确实在Racket中重新实现了C语言。 I / O子系统,并发子系统(包括“绿色”线程的调度程序,并发ML风格的事件和保管人)以及正则表达式匹配器均已在Racket中重写。这些部分是在Racket中重写宏扩展器之后进行的。 Chez Scheme的实现中已经在Scheme中编写了其他需要移出C的内容,例如编译器和对Racket从Scheme继承的数字的广泛支持。

该过程的很大一部分是了解要在Racket中实施什么,在Chez Scheme中实施什么以及在翻译中引入哪些新层。这项工作和重组有益于其他Racket实施工作,例如Pycket和RacketScript。

除了垃圾收集器和运行时系统的类似低级部分外,Racket的许多实现都受益于更高级别的抽象。用C语言编写宏扩展器是一个特别糟糕的选择,因为更高级别的抽象显然使树的操作更加容易,但是同样的原因也适用于I / O层或数字原语。现在,甚至Chez Scheme的[Racket变体]中的垃圾收集器也由用Scheme编写的规范和编译器实现了一半。

另一个重要的优点是Racket社区中有很多Racket程序员,而不是C程序员。说服Racket爱好者看一下Racket或Chez Scheme中的某些代码,并尝试找出一些漏洞或新功能来进行贡献,会更容易。喜欢用C语言读写代码的人可能在为C编译器做出贡献。

最具挑战性的部分不是真正的一部分,而是整体规模。球拍是一门重要的语言,在新的实现中,球拍都必须具有相同的功能。这意味着不仅获得正确的结果和/或特定类型的错误消息,而且获得具有相同或更好性能特征的结果。例如,如果一个宏生成了一个巨大的扩展,但仍然在合理的时间内在Racket BC中进行了编译,则它需要在合理的时间内在Racket CS中进行编译。

对于我们必须实现的特定部分,也许最具挑战性的是向编译器添加类型重建,增加对连续标记的支持,允许记录值充当过程,重新实现Racket的I / O以及升级Chez Scheme的垃圾收集器支持内存记帐,大堆的就地标记和并行性。

Racket BC和Racket CS之间已经有很多小错误和不兼容的修复程序。同时,Racket CS的性能也得到了改善,现在这两个变体的端到端性能都更加一致。 Chez Scheme作为后端,生成代码的速度很少出现问题,但是在Racket中新实现的层需要大量调整。因此,以前在BC中更快的事物现在在CS中通常差不多,而在CS中更快的事物甚至更快。

在Chez Scheme级别上最重要的改进之一是发烟盒拆箱。直到最近,浮点数都存储在引擎盖下的盒子状对象中,因此可以将它们用于期望引用对象的向量或其他容器中。现在,在许多情况下,编译器会检测到不需要该框并跳过它。这减少了分配数量,并提高了使用大量浮点数的程序的速度。

另一个大的改进领域是垃圾收集器。当我们启动Racket CS时,Chez Scheme拥有一个非常出色的收藏家,在传统Scheme程序上表现非常出色。但是Racket需要收集器提供更多功能。我们已经改进了对大堆,从增量收集中受益的GUI和类似游戏的情况以及具有并行性的程序的支持。

您可以从下载页面下载Racket CS,它是当前版本Racket BC的下拉替代品。 Racket BC和Racket CS都包含所有库和同样用Racket编写的IDE。除外部功能界面的某些角落外,用Racket编写的所有代码都应在BC或CS版本中运行,而无需更改。

Gustavo在大学里用它来编辑从Moodle服务器到另一个Moodle服务器的移动测验。 mdz文件就像.tar.gz文件一样,因此您可以使用Racket中的标准库来解压缩它们,编辑其中的xml文件,然后将结果重新打包为新的mdz文件。 (这算作“外部学术界”吗?)

正在使用Racket的最大站点是Hacker News https://news.ycombinator.com,这是一个有关编程和相关主题的论坛。它是用自己的称为Arc的语言编程的,该语言在Racket中编程。他们每天的点击量超过550万,每月的独立访问者介于4百万至5百万之间。他们仍然使用BC版本。

为了瞄准CS,您最终修补了该语言以适应与Racket的差异,这是从一开始的目的吗?两种语言之间的最大区别是什么?

从一开始就考虑了这种可能性。球拍和Chez计划之间存在一些意见分歧,因此早晚要对球拍有用的一些更改不会在Chez计划中有用。

例如,在检查函数是否在期望单个值的位置返回单个值时,Racket更为严格。在Scheme标准中这是未定义的情况,因此Chez Scheme有时会忽略它。在函数实际返回单个值的常见情况下,Chez Scheme可能会更快一些,而在其他情况下,Racket可能会报告更好的错误。这是设计决定,每个团队都有不同的偏好。因此,Racket中的CS版本具有一些附加检查来跟踪单个返回表达式,并在可能时避免不必要的检查。

可以公平地说,Chez Scheme运行时系统针对功能编程进行了优化。例如,在对象修改上存在写障碍,并且当变量不突变时,编译器会更快乐。可以公平地说,它是为具有足够的内存和计算能力的设置而设计的,而不是受更多限制的嵌入式设置。

Gustavo在这次改写中的贡献在于为Racket中使用的Chez Scheme补丁版提供了类型恢复通道。它减少了类型的运行时检查的次数,因此最终代码更快。仍然有很大的空间可以使从Racket到Chez Scheme的翻译更加优化器友好,并且优化通过的翻译也更加友好,因此我希望明年在该领域的表现有所提高。此外,我们可以改善与球拍的高级部分(如合同系统和类型球拍)使用类型的合作。

在Chez Scheme中编写优化步骤会打开很多可能性,例如Gustavo希望添加一些转义分析以使用该信息来避免复制或创建时间结构,以及其他类似的简化方法。在C中尝试使用它太可怕了,但是用Chez Scheme编写它似乎更有可能。 [大概在2022年。]

Matthew已经用完了眼前的任务,他希望在编译器和运行时系统级别上花费更少的时间。既然Racket CS总体上已经赶上并稳定下来了,他希望通过Rhombus项目进一步回到语言设计上。

有扩展Racket的计划吗?在哪些地区?您想在5年后在哪里看球拍?

Rhombus项目是一个实验,首先从表面语言和库设计开始,同时保留了我们现在在Racket中拥有的所有语言构建结构,编译管道和运行时系统。由于Racket CS的开发继续主导着我们的努力,现在还为时过早,并且比我们想象的要早。但是,如果这种设计成功,那么我们希望这将成为Racket的下一个方向。

最初,有些人担心在Racket和硬件之间添加新层会降低性能。初始版本的速度较慢,但​​当前版本的速度类似,在许多基准测试中,速度更快。最有可能的是,一月份的Racket版本将是8.0版,默认版本为Racket CS(基于Chez Scheme)。

其他人担心,因为在制作新版本的同时,Racket的低级部分的开发会放慢一段时间。现在CS版本几乎具有等效的性能,因此希望CS版本中开发速度的提高将弥补延迟。无论如何,Racket是一个很大的项目,因此在开发CS版本时,其他领域也有了很大的改进,例如新的库或更有效的合同系统,并且所有这些新功能在两个版本中都可用。

我们当然会推荐“如何设计程序”(https://htdp.org)。尽管它主要是针对初级程序员的,但它也可以为还没有丰富经验的程序员提供函数式编程速成课程。 Dan Friedman和Mitch Wand撰写的《编程语言要点》是从与HtDP相同的角度学习编程语言的经典著作。

Matthew不建议直接编程,但建议使用Nadia Eghbal的《公开工作:开源软件的制作和维护》。该书对开放源代码软件的历史和状态进行了清醒的反映-部分是开放源代码的概念,但特别是该想法在实践中如何发挥作用。

古斯塔沃(Gustavo)想推荐巴赫(Gödel),埃舍尔(Escher),巴赫(Bach):永恒的金色辫子。它与Racket无关,但是它对将公式/代码转换为数字/数据进行了一些不错的讨论,这是LISP系列宏背后的主要思想之一。