Общепринято, что std::unique_ptr
не приводит к снижению производительности ( и не к потере памяти, если не используется параметр удаления ), но недавно я наткнулся на обсуждение, показывающее что он фактически вводит дополнительную косвенность, потому что unique_ptr
нельзя передать в регистр на платформах с Itanium ABI. Опубликованный пример был похож на
#include <memory>
int foo(std::unique_ptr<int> u) {
return *u;
}
int boo(int* i) {
return *i;
}
, который генерирует дополнительную инструкцию ассемблера в foo по сравнению с boo.
foo(std::unique_ptr<int, std::default_delete<int> >):
mov rax, QWORD PTR [rdi]
mov eax, DWORD PTR [rax]
ret
boo(int*):
mov eax, DWORD PTR [rdi]
ret
Объяснение состояло в том, что Itanium ABI требует, чтобы unique_ptr
не передавался в регистр из-за нетривиального конструктора, поэтому он создается в стеке, а затем адрес этого объекта передается в регистре.
Я знаю, что это не сильно влияет на производительность на современной платформе ПК, но мне интересно, может ли кто-нибудь предоставить более подробную информацию о причинах, по которым он не будет скопирован в реестр. Поскольку абстракции с нулевой стоимостью являются одной из основных целей C ++, мне интересно, обсуждалось ли это в процессе стандартизации как допустимое отклонение или это вопрос качества реализации. Потеря производительности, безусловно, достаточно мала, если учесть преимущества, особенно на современных платформах ПК.
Комментаторы отметили, что эти две функции не полностью эквивалентны, и поэтому сравнение некорректно, поскольку foo
также вызовет средство удаления для параметра unique_ptr
, но boo
не освобождает память. Однако меня интересовала только разница, возникающая при передаче значения unique_ptr
по сравнению с передачей простого указателя. Я изменил пример кода и включил вызов delete
, чтобы освободить простой указатель ; вызов находится в вызывающей стороне, поскольку средство удаления unique_ptr
также вызывается в контексте вызывающей стороны, чтобы сделать сгенерированный код более идентичным. Кроме того, руководство delete
также проверяет ptr != nullptr
, потому что деструктор также делает это. Тем не менее, foo
не передает параметр в регистр и должен
сделать косвенный доступ.
Мне также интересно, почему компилятор не отменяет проверку для nullptr
перед вызовом operator delete
, так как в любом случае это определено как noop. Я думаю, что unique_ptr
может быть специализированным для средства удаления по умолчанию, чтобы он не выполнял проверку в деструкторе, но это было бы очень маленькой микрооптимизацией.