Raku中类型/约束的性能惩罚



与Perl5相比,Raku引入了渐进式键入。逐渐类型化的面向对象语言非常丰富,包括:typed Racket、C#、StrongScript、Reticulated Python。

据说";在没有额外运行时成本的情况下进行可选的渐进类型检查";在Raku官方网站上。据我所知,一些渐进式键入语言(如Typed Racket和Reticulated Python)由于强制类型系统健全性的策略而遭受了严重的性能问题。另一方面,StrongScript中的具体类型由于相对便宜的标称子类型测试而表现良好。渐进分型(不包括Raku)的分类研究:

StrongScript中的

C#具体类型:对类型构造函数使用运行时子类型测试来补充静态类型。当静态类型化的代码以本机速度执行时,在类型化的非类型化边界处动态检查值。类型插入有效的强制转换,并生成可以优化的代码。它们也很好,开销很低,但在表现力和从非类型化迁移到类型化的能力方面是有代价的。

类型化机架:监视值以确保它们的行为符合指定的类型。包装器不检查像concrete这样的静态类型标记的高阶和可变值,而是确保值与其声明类型的持久一致性。它避免了类型化代码中的强制转换。然而,它为这种可靠性付出的代价是,在类型化的非类型化的边界处插入了重量级的包装器。

网状Python:介于两者之间;它添加了类型转换,但仅针对顶级数据结构这样做。Reticulated Python的瞬态语义的性能对于具体类型来说是最糟糕的情况——即,几乎每个调用都有一个强制转换。它在使用时检查类型,因此向程序中添加类型的行为会引入更多的强制转换,并可能降低程序的速度(即使是在完全类型化的代码中)。

Raku的运行时强制策略是否类似于StrongScript中的C#和具体类型,或者它是否有自己的一套策略来确保不存在明显的性能问题,如Typed Racket和Reticulated Python?它有健全的渐进式系统吗?

Raku命令写入程序的类型约束最晚在运行时强制执行。如何实现这一承诺取决于编译器和运行时实现者。我将讨论Rakudo(编译器)和MoarVM(运行时)配对是如何做到这一点的,因为这就是我所研究的

初始编译本身在分析方面几乎没有做什么来消除类型检查,因此我们生成的字节码中有很多类型检查。这里的赌注是分析需要时间,只有一些代码会发现自己处于热路径上(或者对于非常短的脚本,没有热路径),所以我们不妨让VM来判断什么是热的,然后专注于这些比特。

VM执行现代运行时所做的典型分析,不仅记录哪些代码是热门代码,还记录参数类型、返回类型、词法类型等的统计信息。尽管可能会出现大量潜在的动态,但在给定的应用程序中,现实是大量的代码是单态的(只有一种类型,或者对于例程,只有一个参数类型的元组)。另一个群体是多态的(可以看到一些不同的类型),相对少量的是超多态的(大量的类型)。

基于所获得的数据,运行时生成专门化:基于对将显示的确切类型的假设编译的代码版本。保护精确的类型比必须关心子类型关系等等要便宜。因此,在这一点上,我们得到了一个代码版本,其中我们有一些廉价的前置条件,并使用它们来消除更昂贵的类型检查(以及一些分散在代码中的额外保护,以取代其他类型检查)。然而,这并不是真正免费的。。。然而

当打电话时,可能会发生以下两种情况之一:

  • 对于小型被调用者,会进行内联。我们内联了被调用者的一个专门化。如果调用者中的类型知识已经足以证明类型假设(通常是这样),那么就不需要任何保护。从本质上讲,被调用者中的类型检查只是免费的。我们可以将多个级别深入内联。此外,内联使我们能够跟踪通过被调用者的数据流,这可能使我们消除进一步的保护,例如关于被调用者中的返回值类型
  • 对于较大的被调用者,我们可以执行专门化链接,也就是说,直接调用专门化并绕过其保护,因为我们可以使用调用者中的类型知识来证明我们满足保护假设。同样,被调用方参数类型检查因此变得免费

但是那些不是调用的类型y的东西呢,比如返回值类型检查和赋值?我们也将这些编译为调用,这样我们就可以重用相同的机制。例如,返回类型检查,在它是单态的情况下(通常),会变成一个guard+对标识函数的调用,只要我们能证明这个guard,它就会变成标识函数,这是一个微不足道的内联。

还有更多的事情要做。注意:

  • 我上面描述的机制是围绕各种缓存和防护树构建的,它并不像我所说的那么漂亮。有时候,一个人需要建造丑陋的东西,才能学会如何建造美好的东西。值得庆幸的是,目前的一系列工作正在将所有这些学习内容折叠成一个新的、统一的、保护和调度机制,该机制还将处理当今优化非常差的语言的各个方面。这将在几个月内发布
  • 当前运行时已经进行了一些非常有限的转义分析和标量替换。这意味着它可以跟踪数据流到短命对象中,从而找到更多的类型检查来消除(除了消除内存分配之外)。目前正在努力使其更强大,提供部分转义分析、传递性分析,以标量取代整个对象图,从而能够通过它们跟踪数据流等类型

去年发表了一篇题为《瞬态类型检查(几乎)免费》的论文。这根本不是关于Raku/Rakudo/MoarVM,但这是我在学术文献中看到的与我们正在做的事情最接近的描述。那是我第一次意识到,也许我们在这个领域正在做一些创新。:-)

现在jnthn已经为Rakudo和MoarVM写了一篇关于2020年情况的权威综述,我觉得可以发表一篇相当于非专家撰写的涵盖2000年至2019年的历史笔记,这可能会引起一些读者的兴趣。

我的笔记是为了回应你的问题摘录而整理的:

Raku中类型/约束的性能惩罚?

不应该有处罚,相反。也就是说,拉里·沃尔在(2001)早期的设计文档中写道:

为提供更多类型信息,从而提高性能和安全性

(这是在2005年学术会议上引入"渐进打字"一词的4年前。)

因此,他的意图是,如果开发人员添加了一个合适的类型,程序运行起来要么更安全,要么更快/更精简,或者两者兼而有之。

(和/或能够在与外语的互操作中使用:"除了性能和安全性,类型信息有用的另一个地方是编写与其他语言的接口。"十年后,他说类型的第一和第二个原因是多重调度和文档。)

我不知道有什么系统的努力来衡量Rakudo在多大程度上实现了设计意图,即类型永远不会减慢代码的速度,如果它们是本地类型,则可以预见地加快代码的速度。

此外,Rakudo仍在相对快速地变化,十年前的总体年度业绩改善幅度为2-3倍。

(虽然Rakudo已有15年历史,但随着Raku语言的发展,它也得到了发展——最终在过去几年里安定下来了——Rakudo语言发展的总体阶段是"让它工作,让它工作正确,让它快速工作"的1-2-3,后者在最近几年才真正开始出现。)

据我所知,一些渐进式键入语言(如Typed Racket和Reticulated Python)由于强制类型系统健全性的策略而遭受了严重的性能问题。

从理论到实践的逐步打字(2019)总结了2015年的一篇论文,该论文称:

衡量[稳健性成本]的第一次系统性努力。。。揭示了实质性的性能问题。。。

。。。(大概是你一直在读的那些)。。。。

[并且]使用JIT编译器、标称类型、representation改进和定制编译程序以及其他可以显著提高性能。。。

现在将他们的上述性能配方与Rakudo和Raku的特性进行比较:

  • Rakudo是一个有15年历史的自定义编译编译器,具有多个后端,包括带有x86JIT的自定义MoarVM后端

  • 拉库语有一个(渐进的)标称类型系统。

  • Raku语言支持表示多态性。这就像是所有表征改进之母,不是从一体的意义上说,而是从从结构中抽象表征的意义上讲,因此可以通过表征多态性带来的自由来改进。

  • 还有其他潜在类型的系统相关绩效贡献;例如,我希望本机数组(包括多维数组、稀疏数组等)有一天会成为一个重要的贡献者。

另一方面,StrongScript中的具体类型由于相对便宜的标称子类型测试而表现良好

我注意到jnthn的评论:

保护精确类型比必须关心子类型关系等更便宜

我的猜测是,Rakudo是否正在或有一天会提供足够的性能,以使其渐进式打字普遍具有吸引力,陪审团将在未来5年左右的时间里悬而未决。

也许在未来一年左右的时间里,一位陪审员(hi Nile)将第一个就拉库语(do)与其他逐渐打字的语言相比如何得出一些初步结论?

健全性

它有一个健全的渐进式系统吗?

从某种意义上说,有数学处理吗?我99%肯定答案是否定的。

从某种意义上说,认为是健全的?唯一假定的保证是内存安全吗?我想是的。还有别的吗?好问题。

我只能说,afaik Raku类型的系统是由Larry Wall和Audrey Tang等黑客开发的。(参见她2005年关于类型推断的注释。)

最新更新