与C++中的普通指针相比,智能指针的开销是多少?

Ven*_*emo 88 c++ performance smart-pointers c++11

与C++ 11中的普通指针相比,智能指针的开销是多少?换句话说,如果我使用智能指针,我的代码会变慢吗?如果是这样,速度会慢多少?

具体来说,我问的是C++ 11 std::shared_ptrstd::unique_ptr.

显然,推下堆栈的东西会变得更大(至少我认为是这样),因为智能指针也需要存储其内部状态(引用计数等),问题是,这是多少影响我的表现,如果有的话?

例如,我从函数而不是普通指针返回一个智能指针:

std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();
Run Code Online (Sandbox Code Playgroud)

或者,例如,当我的一个函数接受智能指针作为参数而不是普通指针时:

void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);
Run Code Online (Sandbox Code Playgroud)

lis*_*rus 162

std::unique_ptr 只有当你为它提供一些非平凡的删除器时才会有内存开销.

std::shared_ptr 总是有参考计数器的内存开销,虽然它非常小.

std::unique_ptr 只有在构造函数期间(如果它必须复制提供的删除器和/或null初始化指针)和析构函数(以销毁拥有的对象)时才有时间开销.

std::shared_ptr在构造函数(创建引用计数器),析构函数(减少引用计数器并可能销毁对象)和赋值运算符(增加引用计数器)中有时间开销.由于线程安全保证std::shared_ptr,这些增量/减量是原子的,因此增加了一些额外的开销.

请注意,在解除引用(获取对拥有对象的引用)时,它们都没有时间开销,而此操作似乎是指针最常见的操作.

总而言之,有一些开销,但它不应该使代码变慢,除非你不断创建和销毁智能指针.

  • `unique_ptr`在析构函数中没有开销.它与原始指针完全相同. (10认同)
  • @ R.MartinhoFernandes比较原始指针本身,它确实在析构函数中有时间开销,因为原始指针析构函数什么都不做.与可能使用原始指针的方式相比,它肯定没有开销. (5认同)
  • @MK。只有引用计数是原子的,指针本身存储为简单的原始指针。因此,取消引用期间不会影响性能。 (4认同)
  • 您确定“std::shared_ptr”在取消引用对象时不会产生任何开销吗?据我所知,“shared_ptr”指向一个代理对象,它保存一对:{引用计数,指向实际对象的指针}。因此,您需要在内存中执行两次跳转,而不是一次才能到达您的对象。 (3认同)
  • 值得注意的是,shared_ptr构造/销毁/分配成本的一部分是由于线程安全 (2认同)

Che*_*Alf 24

与所有代码性能一样,获得硬信息的唯一真正可靠的方法是测量和/或检查机器代码.

这就是说,简单的推理说

  • 你可以期待调试版本中的一些开销,因为例如operator->必须作为函数调用执行,以便你可以进入它(这反过来又是由于一般不支持将类和函数标记为非调试).

  • 因为shared_ptr你可以期望在初始创建时有一些开销,因为这涉及动态分配控制块,并且动态分配比C++中的任何其他基本操作要慢得多(尽可能使用make_shared,以最小化开销).

  • 另外,对于shared_ptr维护引用计数有一些最小的开销,例如,当传递一个shared_ptrby值时,但是没有这样的开销unique_ptr.

记住第一点,在测量时,为调试和发布版本执行此操作.

国际C++标准化委员会已经发布了一份关于性能技术报告,但这是在2006年之前,unique_ptr并且shared_ptr被添加到标准库中.不过,智能指针在那时仍然是旧帽子,所以报告也考虑了这一点.引用相关部分:

"如果通过普通的智能指针访问值比通过普通指针访问它要慢得多,那么编译器就无法有效地处理抽象.在过去,大多数编译器都有很大的抽象惩罚,而且目前的编译器仍有一些.但是,据报道至少有两个编译器的抽象罚分低于1%而另一个编译器罚款3%,因此消除这种开销完全属于现有技术水平.

作为一个明智的猜测,截至2014年初,当今最流行的编译器已经实现了"最先进的技术".

  • 我看到的一个问题是,一旦在服务器中使用了shared_ptrs,那么shared_ptrs的使用就会开始激增,很快shared_ptrs就成为默认的内存管理技术。所以现在你已经重复了 1-3% 的抽象惩罚,并且一遍又一遍地进行。 (2认同)

Lot*_*har 20

我的答案与其他答案不同,我真的很想知道他们是否曾编写过代码.

shared_ptr具有很大的创建开销,因为它是控制块的内存分配(它将ref计数器和指针列表保存到所有弱引用).它也有巨大的内存开销,因为std :: shared_ptr总是一个2指针元组(一个到对象,一个到控制块).

如果将shared_pointer作为值参数传递给函数,那么它将比正常调用慢至少10倍,并在代码段中创建大量代码以进行堆栈展开.如果你通过引用传递它,你会得到一个额外的间接,这在性能方面也会更差.

这就是为什么你不应该这样做,除非该功能真正涉及所有权管理.否则使用"shared_ptr.get()".它的目的不是确保在正常的函数调用期间不会杀死您的对象.

如果你发疯并在编译器中的抽象语法树之类的小对象上使用shared_ptr,或者在任何其他图形结构中的小节点上使用shared_ptr,你会看到巨大的性能下降和巨大的内存增加.我见过一个解析器系统,在C++ 14上市之后不久,程序员学会正确使用智能指针之前,它就被重写了.重写比旧代码慢了很多.

根据定义,它不是一个银弹,原始指针也不错.糟糕的程序员是糟糕的,坏的设计是坏的.谨慎设计,设计时考虑到明确的所有权,并尝试主要在子系统API边界上使用shared_ptr.

如果您想了解更多信息,可以观看Nicolai M. Josuttis关于"C++中共享指针的实际价格"的好话题https://vimeo.com/131189627
它深入探讨了写入障碍的实现细节和CPU架构,原子锁等等一旦听完,你永远不会谈论这个功能便宜.如果你只想要一个较慢的数据证明,跳过前48分钟并观察他运行的示例代码在任何地方使用共享指针时运行速度慢180倍(用-O3编译).

  • 听说过`std :: make_shared()`吗?另外,我发现公然滥用的示威行为很无聊... (6认同)
  • “make_shared”所能做的就是保护你免受一次额外的分配,如果控制块被分配在对象的前面,会给你更多的缓存位置。当您传递指针时,它根本没有帮助。这不是问题的根源。 (4认同)
  • 感谢您的回答!您在哪个平台上建立了个人资料?你能用一些数据来支持你的说法吗? (2认同)
  • @imallett 不,他没有。他询问了共享 ptr,他的示例都使用了共享 ptr,因为这是真正重要的用例。对于独特的 ptr,他应该在 youtube 上观看“CppCon 2019:Chandler Carruth “There Are No Zero-cost Abstractions”。我会将其添加到答案中。 (2认同)

Cla*_*dgz 12

换句话说,如果我使用智能指针,我的代码会变慢吗?如果是这样,速度会慢多少?

慢点?最有可能的是,除非你使用shared_ptrs创建一个巨大的索引,而你没有足够的内存使你的计算机开始起皱,就像一位老太太被来自远方的难以忍受的力量猛烈地撞倒在地.

使代码变慢的原因是搜索速度缓慢,不必要的循环处理,大量数据副本以及对磁盘的大量写入操作(如数百个).

智能指针的优点都与管理有关.但是必要的开销是多少?这取决于您的实施.假设您正在迭代3个阶段的数组,每个阶段都有1024个元素的数组.smart_ptr为这个过程创建一个可能是过度的,因为一旦迭代完成,你就会知道你必须删除它.所以你可以通过不使用smart_ptr... 来获得额外的记忆

但你真的想这样做吗?

单个内存泄漏可能会使您的产品及时发生故障(假设您的程序每小时泄漏4兆字节,打破计算机需要几个月的时间,然而,它会破坏,您知道因为漏洞存在) .

就像说"你的软件保证3个月,然后,叫我服务."

所以最后这真的是......你能处理这种风险吗?使用原始指针处理数百个不同对象的索引值得失去对内存的控制.

如果答案是肯定的,那么使用原始指针.

如果您甚至不想考虑它,那么这smart_ptr是一个好的,可行的,非常棒的解决方案.

  • 我使用unique_ptr,它简化了很多事情,但不喜欢shared_ptr,引用计数不是很有效的GC,也不完美 (3认同)
  • 可以,但是valgrind在检查可能的内存泄漏方面*好*,因此,只要您使用它,就应该是安全的™ (2认同)

Cae*_*sar 7

unique_ptrChandler Carruth在 2019 年 Cppcon 演讲中提出了一些令人惊讶的“发现” 。(YouTube)。我也无法解释清楚。

我希望我正确理解了两个要点:

  • 没有 will 的代码unique_ptr(通常是错误的)不会处理传递指针时未传递所有权的情况。重写它以使用unique_ptr会增加处理,并且会产生一些开销。
  • Aunique_ptr仍然是一个 C++ 对象,调用函数时对象会在堆栈上传递,而不像指针可以在寄存器中传递。