Rust 零成本的抽象

图片来自 pexels.com

零成本抽象的概念对于某些编程语言非常重要,比如 Rust 和 C++,这些语言的目的是使用户能够用相对较少的努力编写具有出色性能的程序。因为这个概念是 Rust 设计和我的工作的基础,所以我想研究一下,零成本抽象到底是什么。

C++ 的最初开发者Bjarne Stroustrup在其原文中总结了这个想法:

你不用的东西,就不用付钱。而且: 你所使用的代码是最好的。

在这个定义中,有两个因素使某些东西成为一个适当的零成本抽象:

  • 没有全局开销: 零成本抽象不应该对不使用它的程序的性能产生负面影响。例如,它不能要求每个程序都带有一个沉重的语言运行时,以使唯一使用该特性的程序受益。
  • 最优性能: 一个零成本的抽象应该被编译成最佳的解决方案实现,而这个解决方案是有人用较低级别的原语编写的。它不能引入没有抽象就可以避免的额外成本。

然而,我认为重要的是要记住,第三个要求是什么东西是零成本抽象。它经常被忽视,因为它只是所有好的抽象的一个要求,无论是否为零:

  • 改进用户体验: 抽象的要点是提供一个新工具,它由较低级别的组件组装而成,使用户能够更容易地编写他们想要编写的程序。与所有抽象一样,零成本抽象实际上必须提供比其他抽象更好的体验。

当然,所有这些都是永远无法完全实现的理想,尤其是第三个,嗯,品味是无法解释的。但是对于零成本抽象,第三点实际上特别重要,因为零成本抽象与两个不同的替代方案竞争。一方面,它必须比你自己编写的具有相同性能的代码更好,这是相当清楚的。但另一方面,它也必须比牺牲性能成本和使用非零成本的抽象要好。 这并不意味着我们需要严格地优于非零成本抽象,因为性能成本是一个因素,但是我们确实必须在唾弃的范围内,这样无论你承担多少额外的程序员开销来实现“零成本”,都是值得的。

(我认为,在某种程度上,Rust试图通过使非零成本抽象对于编写来说非常不愉快来欺骗这一点,让人觉得零成本抽象更好。我认为这是一个错误,它损害了语言的整体用户体验,可能只是让一些人根本不使用它,损害了我们的总体目标。)

我还想说,实际上创建一个零成本的抽象来实现这三个目标是非常困难的,也是非常棒的。Rust只有几次真正出色地做到了这一点(所有这些都具有极高的影响力),并且参与了我认为将会是那些成功故事之一的工作 – 当然是 async/await – 这感觉就像手中握着一把火。去年9月,我对一个朋友说,我担心我永远不会做任何工作,像我刚刚做的工作(关于Pin API)那么好,我真的感觉到了。但是,我们很少能实现如此伟大的事情,因为它极其困难,而且还需要相当多的运气(许多问题可能只是没有发现真正的零成本抽象,至少在之前的决策所设置的设计约束内)。

为了更清楚地说明我的意思,我想列出Rust中一些真正伟大的零成本抽象:

  • 所有权和借用。当然,这是最大的一个,在没有垃圾收集器的情况下保证内存和线程安全是Rust最初的巨大成功。
  • 迭代器和闭包api。这是另一个经典。虽然在某些情况下,内部迭代可能会得到更好的优化,但是您可以在片上编写 map、filter、iteration for loops等,并将其优化到与一些手写的 C 相当,这一点绝对令人震惊。
  • Async/await 和 Futures。Futures API是一个重要的例子,因为早期版本的Futures很好地实现了零成本抽象的“零成本”部分,但实际上并没有提供足够好的用户体验来推动采用。 通过添加 Pin来支持异步/等待、跨等待的引用等等,我们已经开发出了一个产品,我真的认为它将解决用户的问题,并使Rust更适合编写高性能的网络服务。
  • Unsafe 和 module 边界。 所有这些,以及 Rust 的每一个成功故事的基础,都是 unsafe 块和隐私的概念,这些概念允许我们使用原始指针操作来构建这些零成本抽象。如果没有这种真正的基本能力来打破本地规则,将系统扩展到 typechecker 无法处理的范围之外,Rust 的所有出色特性都不可能实现。这是零成本抽象,是Rust中所有其他零成本抽象的基础。

在其他领域,我们还没有成功地找到零成本抽象。 这方面的一个例子是作为动态分派多态性解决方案的trait对象。(注意,这里动态分派是目标的一部分,所以虚拟调用的成本不是非零的。) 问题是,对象安全的概念,以及大小和非大小类型的概念,可能仅仅是围绕强制性的一些不良的人机工程学,使得trait对象真的很难处理;当我不得不使用它们的时候,我通常会很生气。至少在过去的18个月里,我一直想深入研究这个问题,但其他事情总是优先考虑。

本文翻译自 Zero Cost Abstractions

发表评论

电子邮件地址不会被公开。 必填项已用*标注

66 − = 59