Jen*_*ens 10 c++ overhead unique-ptr
常识是std::unique_ptr
不引入性能损失(并且不使用删除器参数时不会造成内存损失),但我最近偶然发现了一个讨论,显示它实际上引入了一个额外的间接,因为unique_ptr
无法在平台上的寄存器中传递安腾ABI.发布的示例类似于
#include <memory>
int foo(std::unique_ptr<int> u) {
return *u;
}
int boo(int* i) {
return *i;
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
解释是Itanium ABI要求unique_ptr
由于非平凡的构造函数而不能在寄存器中传递,因此它在堆栈上创建,然后该对象的地址在寄存器中传递.
我知道这并没有真正影响现代PC平台的性能,但我想知道是否有人可以提供更多详细信息,说明为什么不能将其复制到寄存器中.由于零成本抽象是C++的主要目标之一,我想知道这是否已经在标准化过程中被讨论为可接受的偏差或者是否是实施质量问题.在考虑其优势时,性能损失肯定很小,特别是在现代PC平台上.
评论者指出,这两个函数并不完全等效,因此比较存在缺陷,因为它foo
也会调用unique_ptr
参数上的删除函数,但boo
不会释放内存.但是,我只对传递一个unique_ptr
by-value与传递一个普通指针所产生的差异感兴趣.我修改了示例代码并包含了一个delete
释放普通指针的调用 ; 调用是在调用者中,因为unique_ptr
调用者的上下文中也调用了删除函数,以使生成的代码更加相同.此外,手册delete
也会检查,ptr != nullptr
因为析构函数也会这样做.但是,foo
不会在寄存器中传递参数并且必须进行间接访问.
我也想知道为什么编译器nullptr
在调用之前不会忽略检查,operator delete
因为无论如何这被定义为noop.我想这unique_ptr
可能是专门用于默认删除器,不在析构函数中执行检查,但这将是一个非常小的微优化.
System V ABI使用Itanium C ++ ABI并对其进行引用。特别是,C ++ Itanium ABI指定
如果出于调用目的参数类型很重要,则调用者必须为临时对象分配空间,并通过引用传递该临时对象。
特别:
...
如果类型具有非平凡的析构函数,则在将全表达式括起来之后,调用者会在控制权返回给它之后(包括调用者抛出异常时)调用该析构函数。
因此,“ 为什么不将其传递到寄存器 ” 这个问题的简单答案是“ 因为它不能 ”。
现在,一个有趣的问题可能是“ 为什么C ++ Itanium ABI决定这样做 ”。
尽管我不会声称自己具有基本的知识,但是我想到了两点: