使用Cell<T>相比于仅使用T会产生哪些成本?

6
我看到一个在reddit上的评论,指出使用Cell<T>会阻止某些优化:

Cell没有内存开销(Cell和T的大小相同),运行时开销小(它“只是”抑制了优化,不会引入额外的显式操作)。

这似乎与我读到的其他关于Cell<T>的东西相反,特别是它是“零成本”的。我第一次遇到这种分类是在这里
总之,我想了解使用Cell<T>的实际成本,包括它可能阻止的任何优化。

2
当LLVM知道某些内容是不可变的时,它可以进行许多优化,如果该内容是可变的,则这些优化将是不安全的。在Rust中,默认情况下所有内容都是不可变的,因此编译器需要向LLVM发出标记,以便它不能随意重新排列代码。 - Peter Hall
4
零成本意味着如果您想要手动实现它,您将具有与抽象相同的成本。如果您需要提供与“Cell”相同的功能,则会有相同的优化损失。 - Stargateur
1
与此相关的是问题#34496,以及UnsafeCell文档(请注意,Cell本身对编译器并不特殊,因为它不是语言项; 它从其内部的UnsafeCell“继承”了这个属性)。 - trent
1个回答

11

TL;DR Cell零开销的抽象;也就是说,手动实现相同功能的成本是相同的。


术语“零成本抽象”不是英语,而是行话。 “零成本抽象”的想法是,“抽象层”本身与“手动”执行相比不会增加任何成本。
有各种误解:最值得注意的是,我经常看到零成本被理解为“操作是免费的”,这并不是事实。
为了增加混淆,大多数C ++实现使用的异常机制(Rust用于 panic = unwind )称为Zero-Cost Exceptions,并声称在非抛出路径上不会添加任何开销。 这是一种不同类型的零成本...
最近,我的建议是切换到使用术语“零开销抽象”:首先,因为它是与零成本例外不同的术语,所以不太可能被误解;其次,因为它强调抽象不会增加开销,这正是我们试图传达的内容。 1 目标仅部分实现。虽然具有和没有抛出可能性的相同程序确实具有相同的性能,但潜在异常的存在可能会妨碍优化器并导致它首先生成次优汇编。
说了这么多,我想了解使用Cell<T>的实际成本,包括它可能阻止的任何优化。
在内存方面,没有额外的开销:
  • sizeof::<Cell<T>>() == sizeof::<T>(),
  • 给定类型为Cell<T>的cell,有&cell == cell.as_ptr()
(你可以查看源代码
在访问方面,Cell<T>T相比确实会产生运行时成本;额外功能的成本。
最直接的成本是通过&Cell<T>对值进行操作需要来回复制1。这是一次按位复制,所以如果优化器可以证明安全,它可以省略它。
另一个显著的成本是,UnsafeCell<T>(Cell<T>的基础)违反了&T表示T不能被修改的规则。
当编译器可以证明一部分内存不会被修改时,它可以优化掉进一步的读取:在寄存器中读取t.foo,然后使用寄存器值而不是再次读取t.foo
在传统的Rust代码中,&T给出了这样的保证:无论是否有不透明的函数调用、调用C代码等,在两次读取t.foo之间,第二次读取将返回与第一次相同的值,得到保证。但是,使用&Cell<T>就没有这样的保证,因此除非优化器能够证明该值未被修改2,否则它不能应用这样的优化。 1您可以通过&mut Cell<T>或使用unsafe代码免费操作该值。 2例如,如果优化器知道该值驻留在堆栈上,并且它从未将该值的地址传递给任何其他人,则可以合理地推断出没有其他人可以修改该值。虽然肯定存在堆栈破坏攻击。

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