普遍的智慧是std::unique_ptr不会带来性能损失 (不使用删除器参数时不会造成内存损失),但我最近偶然发现了一个讨论,表明它实际上引入了一个额外的间接,因为unique_ptr
无法在具有 Itanium ABI 的平台上的寄存器中传递。发布的示例类似于
#include <memory>
int foo(std::unique_ptr<int> u) {
return *u;
}
int boo(int* i) {
return *i;
}
与 boo 相比,它在 foo 中生成额外的汇编指令。
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
解释是安腾 ABI 要求unique_ptr
由于构造函数不平凡,因此不应在寄存器中传递,因此它在堆栈上创建,然后将该对象的地址传递在寄存器中。
我知道这并不会真正影响现代 PC 平台的性能,但我想知道是否有人可以提供更多详细信息,说明为什么不应将其复制到寄存器。由于零成本抽象是 C++ 的主要目标之一,我想知道这是否已在标准化过程中作为可接受的偏差进行讨论,或者是否是实现质量问题。考虑到好处时,性能损失肯定足够小,尤其是在现代 PC 平台上。
评论者指出,这两个函数并不完全等效,因此比较是有缺陷的,因为foo
还将调用删除器unique_ptr
参数但是boo
不释放内存。然而,我只对通过考试所带来的差异感兴趣unique_ptr
按值传递与传递普通指针相比。我修改了示例代码并添加了对delete释放普通指针;呼叫在呼叫者中,因为unique_ptr
的删除器也会在调用者的上下文中被调用,以使生成的代码更加相同。此外,手册delete
还检查ptr != nullptr
因为析构函数也会这样做。仍然,foo
不传递寄存器中的参数,并且必须
进行间接访问。
我还想知道为什么编译器不忽略对nullptr
打电话之前operator delete
因为无论如何这都被定义为 noop 。我猜可能是unique_ptr
可以专门为默认删除器不在析构函数中执行检查,但这将是一个非常小的微观优化。