我碰到了comment on reddit,这表明使用Cell<T>
会阻止某些优化的发生:
Cell工作时没有内存开销(Cell与T大小相同)并且运行时开销很小(“只是”抑制了优化,它没有引入额外的显式操作)
这似乎与我所读过的关于Cell<T>
的其他事情相反,尤其是它的“零成本”。我遇到此分类的第一个地方是here。
话虽如此,我想了解使用Cell<T>
的实际成本,包括可能阻止的任何优化。
答案 0 :(得分:9)
TL; DR Cell
是零开销抽象;也就是说,手动实现的相同功能具有相同的成本。
零成本抽象一词不是英语,而是专业术语。 零成本抽象的想法是,与手动做相同的事情相比,抽象层本身不会增加任何成本。>
各种误解不断涌现:最显着的是,我经常看到零成本被理解为“操作是免费的” ,事实并非如此。
更令人困惑的是,大多数C ++实现所使用的异常机制以及Rust用于panic = unwind
的异常机制被称为零成本异常,并且声称 1 不会增加开销。非投掷路径。这是另一种零成本...
最近,我的建议是转而使用术语零开销抽象:首先是因为它是与零成本异常不同的术语,因此不太可能被误解,其次是因为它强调该抽象没有添加开销,这是我们首先要传达的内容。
1 目标仅部分实现。尽管有或没有抛出可能性执行的同一程序集确实具有相同的性能,但潜在异常的存在可能会阻碍优化器并使其首先生成次佳的程序集。
话虽如此,我想了解使用
Cell<T>
的实际成本,包括可能阻止的任何优化。
在内存方面,没有开销:
sizeof::<Cell<T>>() == sizeof::<T>()
,cell
,Cell<T>
的{{1}}。(您可以窥视at the source code)
在访问端,&cell == cell.as_ptr()
与Cell<T>
相比确实会产生运行时成本;额外功能的成本。
最直接的成本是通过T
操作值需要来回复制 1 。这是按位复制,因此优化程序可以删除它,只要它可以证明这样做是安全的。
另一个值得注意的代价是&Cell<T>
所基于的UnsafeCell<T>
违反了Cell<T>
意味着&T
无法修改的规则。
当编译器可以证明无法修改一部分内存时,它可以优化进一步的读取:在寄存器中读取T
,然后使用寄存器值而不是再次读取t.foo
。 / p>
在传统的Rust代码中,t.foo
提供了这样的保证:无论两次&T
的读取之间是否存在不透明的函数调用,对C代码的调用等...将返回与第一个相同的值(保证)。使用t.foo
时,就不再有这样的保证,因此,除非优化器可以毫无疑问地证明该值是未经修改的 2 ,否则它将无法应用此类优化。
1 您可以通过&Cell<T>
或使用&mut Cell<T>
代码免费操作该值。
2 例如,如果优化器知道该值驻留在堆栈上,并且从未将值的地址传递给其他任何人,那么它可以合理地得出结论:否则可以修改该值。当然,尽管可能会发生堆栈粉碎攻击。