“Zero Cost Abstraction”是什么意思?

34

我在探索 Rust 的时候遇到了这个术语。

我看到了各种不同的解释,但仍然不太明白其中的思想。

嵌入式 Rust 书籍中,它说道:

类型状态也是零成本抽象的一个很好的例子

  • 将某些行为移动到编译时执行或分析的能力。

这些类型状态不包含任何实际数据,而是用作标记。

由于它们不包含任何数据,在运行时内存中也没有实际表示:

这是否意味着运行时更快,因为运行时没有内存?

如果有人能以易懂的方式解释一下,我将不胜感激。


12
零成本抽象并不会使任何东西更快,相反它们(希望)会使运行时与您编写较低级别、未抽象版本的运行时“完全相同”(通常以编译时间为代价)。这个想法通常是添加一个便利的抽象层,但不会产生运行时的任何惩罚。 - Cory Kramer
4个回答

69
Zero Cost Abstractions(零成本抽象)指的是添加诸如泛型、集合等更高级别的编程概念,不会带来运行时成本,只有编译时成本(代码编译速度会变慢)。对于零成本抽象的任何操作,其速度与使用低级别编程概念(如for循环、计数器、if条件语句和使用原始指针)手写匹配的功能一样快。
换句话说,使用零成本抽象工具,函数、模板、类等的使用对你的代码性能没有“成本”。

16

零成本抽象是指在执行速度或内存使用方面没有运行时成本的抽象。

相比之下,虚拟方法是一个很好的例子,它是一种昂贵的抽象:在许多面向对象语言中,方法调用者的类型是在运行时确定的,这需要维护一个查找表(运行时内存使用),然后实际执行查找(每个方法调用的运行时开销,可能至少需要额外的指针解引用)以确定调用哪个版本的方法。另一个很好的例子是垃圾回收:为了不必担心内存分配的细节,你需要付出GC暂停的代价。

然而,Rust大多数情况下都试图采用零成本抽象:这些抽象可以让你既能拥有优雅的代码,又能保证编译器能够安全、正确地将其转换为不带有额外间接性/内存使用的形式。事实上,我所知道的(如果我错了,请更加了解的人纠正我)在Rust中真正需要在运行时支付的唯一费用是边界检查和dyn(请参见上面关于虚拟方法的部分)。


4
Rust拥有特质(trait),可以提供运行时的虚函数行为,例如(参见此处)。 - rubenvb
2
嗯,看起来 Rust 在某种意义上是“聪明的”,因为它会在可能的情况下“去虚拟化”特质使用(至少我用我的 C++ 视角阅读了这篇博客文章)。我认为这归结于 Rust 为静态和动态分派提供统一的语法。 - rubenvb
4
特征可以在运行时或编译时分派。 如果您有一个“结构体Foo:Bar”,以及一个变量“let foo = Foo {}”,则调用“Bar”中的方法将在编译时(零成本)分派。 但是,如果您的变量是“Bar”引用:“let bar =&foo as&Bar”,则分派将使用与大多数面向对象语言相同的查找表在运行时完成(除非优化器能够返回到“Foo”类型,某些面向对象语言也可以这样做)。 - Jmb
1
@Jmb 当你使用 dyn,比如 &dyn Trait 时,Rust 才会使用动态分发。否则,即使是引用,它也总是使用编译时分发。 - Dev
1
@Dev 如果您查看 此代码示例 的汇编代码,您会注意到对 bar.bar() 的调用实际上是通过内联方式进行的,而不是使用 &dyn Bar 虚函数表。这是因为编译器足够聪明,能够识别出 bar 始终引用一个 Foo 实例,即使它是一个 trait 对象。 - Jmb
显示剩余10条评论

6
零成本抽象概念最初来自于函数式编程领域。然而,术语来源于C ++。根据Bjarne Stroustrup的说法:
一般来说,C ++ 实现遵循零开销原则:您不使用的,您不需要为其付出代价。并且进一步地:您所使用的,没有更好的手工编码方式。
这段引用及大多数答案未能完整地传达这个想法,因为未明确说明这些话所说的上下文环境。
如果世界上只有一种编程语言,无论是 Rust 还是 C++,则零成本抽象将与大多数其他编译器优化难以区分。这里的含义是还有无数其他语言可以让您做与 Rust 或 C++ 相同的事情,但带有非零和通常是运行时特定的成本。

你有支持这源自函数式编程的参考资料吗? - mkrieger1
2
@mkrieger1 不需要参考资料,很明显函数式编程是一种强调使用纯函数、不可变数据和声明式结构来创建易于理解和维护的程序的编程风格,是零成本抽象的完美例子。 - Juliano Silva
2
@JulianoSilva 这取决于你将抽象与何物进行比较。不可变数据意味着你会执行复制写入操作,而不是原地操作(这也是为什么函数式编程中的游戏和矩阵计算代价高昂的原因)。它还需要垃圾回收(或其他自动内存管理)来清理未使用的数据(尽管从技术上讲,Haskell 的垃圾回收“保存已使用的数据”而不是“收集垃圾”)。 - gust
1
@gust 你说得对,让我修正一下我的评论:零成本抽象并非特定于函数式范例,它往往需要在数据上进行记忆以提高效率。相反,是将带有数据和行为的实体一起用于更高级别的实现,使零成本抽象变得有用,例如面向对象编程、元编程和泛型编程。 - Juliano Silva

-1
零成本抽象是指在代码中使用既表达性又高效的抽象,而不会产生任何额外的运行时开销。这个概念特别与C++编程语言相关,它是最早开创元编程、泛型编程和面向对象编程的语言之一,所有这些都可以用来实现零成本抽象。
另一方面,我们有函数式编程,虽然它也可以利用抽象,但通常不与零成本抽象相关联,因为它强调不可变性,可能需要额外的内存分配和写时复制操作(根据gust评论),这可能会影响某些用例的性能,例如实时游戏编程或矩阵计算。

@AhmedSbai 你是怎么想到那个的?我根据这篇文章中的先前知识来制作的。 - Juliano Silva
兄弟,这是chatGPT给出的相同答案。 - Ahmed Sbai
虽然我经常使用这个术语,我的语言可能会模仿它,但这并不意味着我直接复制了它,这是基于我的知识。就地操作和写时复制的概念对于回答这个问题非常有帮助。 - Juliano Silva
@EricAya,这是一个非常好的观点。我发现自己在使用技术语言时会进入某种惯性方向,因为我的母语不同,我学习呈现逻辑信息的方式与最佳呈现逻辑信息的方式相当相似,有点类似于人工智能的外观,因为我像没有限制一样处理感知输入,因此,我不会指责任何抱怨的人,这是我有时最好的选择用词的方式。 - Juliano Silva
零成本抽象的概念并不保证表达能力 - 而且你所措辞的方式基本上是误导性的结果。使用抽象的整个原因就是为了表达能力,零成本部分是指这种表达能力不应该以运行时成本(如果你对术语不够准确,可以称之为“效率”)作为交换。进一步暗示函数式语言更容易产生非零成本抽象是一个站不住脚的断言,实际上并不正确。 - Nathaniel Ford
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接