我跑过一个comment on reddit,表明使用Cell<T>
阻止了某些优化的发生:
Cell工作时没有内存开销(Cell与T大小相同)并且运行时开销很小(它“只是”禁止优化,它不会引入额外的显式操作)
这似乎与我读过的关于Cell<T>
的其他事情相反,特别是它是“零成本”。我遇到这种分类的第一个地方是here。
尽管如此,我想了解使用Cell<T>
的实际成本,包括它可能阻止的任何优化。
TL; DR Cell
是Zero-Overhead Abstraction;也就是说,手动实现的相同功能具有相同的成本。
零成本抽象一词不是英语,而是行话。零成本抽象的概念是,与手动执行相同的操作相比,抽象层本身不会增加任何成本。
出现了各种各样的误解:最值得注意的是,我经常看到零成本被理解为“操作是免费的”,但事实并非如此。
更令人困惑的是,大多数C ++实现使用的异常机制以及Rust用于panic = unwind
的异常机制称为零成本异常,并且purports1在非抛出路径上不增加任何开销。这是一种不同的零成本......
最近,我的建议是切换到使用术语零开销抽象:首先因为它是零成本例外的一个独特术语,因此不太可能被误解,其次是因为它强调抽象不添加开销,这是我们首先想要传达的是什么。
1目标只是部分实现。虽然在执行和不执行投掷的情况下执行的相同程序集具有相同的性能,但潜在异常的存在可能会阻碍优化程序并使其首先生成次优装配。
尽管如此,我想了解使用
Cell<T>
的实际成本,包括它可能阻止的任何优化。
在内存方面,没有开销:
sizeof::<Cell<T>>() == sizeof::<T>()
,cell
的Cell<T>
,&cell == cell.as_ptr()
。(你可以偷看at the source code)
在访问方面,与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例如,如果优化器知道该值驻留在堆栈上,并且它从未将值的地址传递给其他任何人,那么它可以合理地得出结论,没有其他人可以修改该值。当然,虽然可能会发生堆栈粉碎攻击。