TL; DR Cell
- Абстракция с нулевыми накладными расходами ; то есть те же функциональные возможности, реализованные вручную, имеют одинаковую стоимость.
Термин Абстракции с нулевой стоимостью это не английский, это жаргон. Идея Абстракции с нулевой стоимостью состоит в том, что слой абстракции сам по себе не добавляет никаких затрат по сравнению с вручную , выполняющим ту же самую вещь.
Возникли различные недоразумения: в частности, я регулярно видел нулевую стоимость, понимаемую как «операция бесплатна» , а это не так.
Чтобы добавить в заблуждение, механизм исключений, используемый большинством реализаций C ++ и который Rust использует для panic = unwind
, называется Исключениями с нулевой стоимостью и подразумевает 1 , чтобы не добавлять накладные расходы на метательный путь. Это другой вид нулевой стоимости ...
В последнее время я рекомендую перейти на использование термина Абстракции с нулевыми накладными расходами : во-первых, потому что это термин, отличный от исключений с нулевой стоимостью, поэтому менее вероятно, что он будет ошибочным, и во-вторых, потому что он подчеркивает, что абстракция не добавляет накладных расходов, что мы и пытаемся передать в первую очередь.
1 Цель достигнута лишь частично. Хотя одна и та же сборка, выполненная с возможностью выброса и без нее, действительно имеет одинаковую производительность, наличие потенциальных исключений может помешать оптимизатору и в первую очередь привести к созданию неоптимальной сборки.
Учитывая все вышесказанное, я бы хотел понять реальную стоимость использования Cell<T>
, включая любые оптимизации, которые он может предотвратить.
На стороне памяти нет служебных данных:
sizeof::<Cell<T>>() == sizeof::<T>()
- с учетом
cell
типа Cell<T>
, &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 Например, если оптимизатор знает, что значение находится в стеке, и никогда не передавал адрес значения кому-либо еще, то он может сделать разумный вывод, что никто другой может изменить значение. Хотя атака с разбиванием стека может, конечно.