与C 11中的普通指针相比,智能指针的开销是多少?换句话说,如果我使用智能指针,我的代码会慢慢变慢吗?
具体来说,我正在询问C 11 std::shared_ptr
和std::unique_ptr
。
显然,将堆栈推下的东西将变得更大(至少我认为是这样),因为聪明的指针还需要存储其内部状态(参考计数等),问题确实是如果有的话,这将影响我的表现?
例如,我从功能而不是普通指针返回智能指针:
std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();
或例如,当我的一个功能接受智能指针作为参数而不是普通指针时:
void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);
std::unique_ptr
只有在向其提供一些非平凡的Deleter时才有内存开销。
std::shared_ptr
总是有内存开销,以便参考计数器,尽管它很小。
std::unique_ptr
仅在构造函数期间才有时间开销(如果它必须复制提供的deleter和/或null initialize指针)和destructor期间(要销毁拥有的对象)。
std::shared_ptr
在构造函数(创建参考计数器),destructor(降低参考计数器并可能销毁对象)和分配运算符(以增加参考计数器)中的时间开销。由于std::shared_ptr
的线程安全保证,这些增量/减少是原子的,因此增加了更多的开销。
请注意,它们都没有时间开销(在获取对拥有对象的引用时),而此操作似乎是指示器最常见的。
总结一下,有一些开销,但是除非您不断创建和破坏智能指针,否则它不应使代码慢。
我答案与其他人不同,我真的想知道他们是否曾经介绍过代码。
shared_ptr具有创建的重要开销,因为它对控制块的内存分配(将REF计数器和指针列表保留到所有弱参考)。由于此,它也有一个巨大的内存开销,并且std :: shared_ptr始终是2个指针元组(一个对象,一个对控制块)。
如果将共享_pointer传递到一个值参数的函数,则它将速度至少要慢10倍,然后在代码段中创建大量代码,以便堆栈放松。如果您通过参考将其传递给它,您会得到一个额外的间接方向,这在性能方面也可能更糟。
这就是为什么您不应该这样做的原因,除非该功能真正参与了所有权管理。否则使用&quot'shared_ptr.get()&quot。它不是为了确保在正常功能呼叫中不会杀死您的对象。
如果您发疯并在编译器中的抽象语法树或任何其他图形结构中的小节点上使用shared_ptr
,您将看到巨大的性能下降和大量的内存增加。我看到了一个解析器系统,该系统在C 14上市后不久就被重写了,并且在程序员学会正确使用智能指针之前。重写比旧代码慢。
这不是银弹,从定义上讲,原始指针也不错。坏程序员很糟糕,坏设计不好。谨慎设计,考虑清晰所有权的设计,并尝试使用子系统API边界上的共享_ptr。
如果您想了解更多信息,您可以观看Nicolai M. Josuttis,很好地谈论" C 中共享指针的实际价格"。https://vimeo.com/131189627
它深入研究了实现细节和CPU架构,用于写障碍,原子锁等。一旦聆听,您将永远不会谈论此功能便宜。如果您只想证明幅度较慢的证明,请跳过前48分钟,然后观察他运行的示例代码,该代码在使用共享指针时运行速度慢了180倍(使用-O3编译)。
编辑:
,如果您询问" std :: simelod_ptr"比参观这个谈话CPPCON 2019:Chandler Carruth"没有零成本的抽象"https://www.youtube.com/watch?v=rhikrotswcc
是不正确的,unique_ptr是100%免费的。
外部:
我试图教育人们的虚假想法,即使用未抛出的例外情况已经二十年来没有成本罚款。在这种情况下,它在优化器和代码大小中。
与所有代码性能一样,获取硬信息的唯一真正可靠的手段是 MEATUR 和/或> INSPECT> INSPECT MANICE代码。
也就是说,简单的推理说
-
您可以期望调试构建中的一些开销,因为例如
operator->
必须作为函数调用执行,以便您可以介入它(这又是由于对标记类和功能的支持通常缺乏支持)。 -
对于
shared_ptr
,您可以期望在初始创建中有一些开销,因为这涉及控制块的动态分配,而动态分配比C 中的任何其他基本操作都要慢得多(在实际上使用make_shared
,最小化开销)。 -
对于
shared_ptr
,在维持参考数量时,有一些最小的开销,例如当按值传递shared_ptr
时,但是unique_ptr
没有这样的开销。
牢记上面的第一个点,当您测量时,对调试和发布构建做到了。
国际C 标准化委员会已经发布了一份有关绩效的技术报告,但这是在2006年,在将unique_ptr
和shared_ptr
添加到标准库中之前。尽管如此,智能指针是那时的旧帽子,因此该报告也考虑了这一点。引用相关部分:
&ldquo;如果 通过微不足道的智能指针访问价值比访问它要慢得多 通过普通的指针,编译器效率低下处理抽象。在里面 过去,大多数编译器都受到了重大的抽象惩罚和几个当前的编译器 仍然这样做。但是,至少两个编译器 据报道有抽象 罚款低于1%,另外罚款3%,因此 消除这种开销是 在艺术的状态下
作为一个明智的猜测,在最新的状态下;截至2014年初,当今最受欢迎的编译器已实现。
换句话说,如果我的代码会更慢。使用智能指针,如果是的话,
的速度较慢?慢?很可能不是,除非您使用shared_ptr创建了一个巨大的索引,并且您的记忆力没有足够的记忆力,以至于您的计算机开始皱纹,就像一个老太太被远处无法忍受的力量掉到了地上一样。
什么会使您的代码较慢的是搜索缓慢,不必要的循环处理,大量数据副本以及磁盘的许多写操作(如数百)。
智能指针的优点都与管理有关。,但是开销是必需的吗?这取决于您的实现。假设您正在迭代3个阶段,每个阶段的数组为1024个元素。为此过程创建smart_ptr
可能是过分的,因为一旦迭代完成,您就会知道必须删除它。因此,您可以通过不使用smart_ptr
...
单个内存泄漏可能会使您的产品有时间的失败点(假设您的程序泄漏每小时4兆字节,需要几个月的时间才能破坏计算机,但是,它会破坏,您知道,因为泄漏泄漏,在那里)。
就像说"您的软件可以保证3个月",然后致电我进行服务。
最终,这确实是一个问题...您能应付这种风险吗?是否使用原始指针来处理超过数百个不同对象的索引值得失去对内存的控制。
如果答案是肯定的,请使用原始指针。
如果您甚至不想考虑它,smart_ptr
是一个很好,可行且很棒的解决方案。
钱德勒·卡鲁斯(Chandler Carruth)有几个令人惊讶的"发现"在unique_ptr
的2019年CPPCON演讲中。(YouTube)。我也不能很好地解释。
我希望我能理解这两个要点:
- 没有
unique_ptr
的代码将(通常是错误的)将无法处理通过指针时未通过OWKOL的情况。将其重写以使用unique_ptr
将添加该处理,并具有一些开销。 -
unique_ptr
仍然是C 对象,并且在调用函数时将在堆栈上传递对象,与Pointer不同,可以在寄存器中传递。
仅用于瞥见,仅适用于[]
操作员,它比以下代码中所示的原始指针慢约5倍,该代码使用gcc -lstdc++ -std=c++14 -O0
编译并输出此结果:
malloc []: 414252610
unique [] is: 2062494135
uq get [] is: 238801500
uq.get()[] is: 1505169542
new is: 241049490
我开始学习C ,我想到了:您总是需要知道您在做什么,并花更多时间知道别人在您的C 中所做的事情。
编辑
@mohan Kumar毒品,我提供了更多详细信息。GCC版本是7.4.0 (Ubuntu 7.4.0-1ubuntu1~14.04~ppa1)
,在使用-O0
时获得了上述结果,但是,当我使用'-O2'标志时,我得到了:
malloc []: 223
unique [] is: 105586217
uq get [] is: 71129461
uq.get()[] is: 69246502
new is: 9683
然后转移到clang version 3.9.0
,-O0
是:
malloc []: 409765889
unique [] is: 1351714189
uq get [] is: 256090843
uq.get()[] is: 1026846852
new is: 255421307
-O2
是:
malloc []: 150
unique [] is: 124
uq get [] is: 83
uq.get()[] is: 83
new is: 54
clang -O2
的结果很棒。
#include <memory>
#include <iostream>
#include <chrono>
#include <thread>
uint32_t n = 100000000;
void t_m(void){
auto a = (char*) malloc(n*sizeof(char));
for(uint32_t i=0; i<n; i++) a[i] = 'A';
}
void t_u(void){
auto a = std::unique_ptr<char[]>(new char[n]);
for(uint32_t i=0; i<n; i++) a[i] = 'A';
}
void t_u2(void){
auto a = std::unique_ptr<char[]>(new char[n]);
auto tmp = a.get();
for(uint32_t i=0; i<n; i++) tmp[i] = 'A';
}
void t_u3(void){
auto a = std::unique_ptr<char[]>(new char[n]);
for(uint32_t i=0; i<n; i++) a.get()[i] = 'A';
}
void t_new(void){
auto a = new char[n];
for(uint32_t i=0; i<n; i++) a[i] = 'A';
}
int main(){
auto start = std::chrono::high_resolution_clock::now();
t_m();
auto end1 = std::chrono::high_resolution_clock::now();
t_u();
auto end2 = std::chrono::high_resolution_clock::now();
t_u2();
auto end3 = std::chrono::high_resolution_clock::now();
t_u3();
auto end4 = std::chrono::high_resolution_clock::now();
t_new();
auto end5 = std::chrono::high_resolution_clock::now();
std::cout << "malloc []: " << (end1 - start).count() << std::endl;
std::cout << "unique [] is: " << (end2 - end1).count() << std::endl;
std::cout << "uq get [] is: " << (end3 - end2).count() << std::endl;
std::cout << "uq.get()[] is: " << (end4 - end3).count() << std::endl;
std::cout << "new is: " << (end5 - end4).count() << std::endl;
}