10

我现在一直在使用共享指针,而且我的程序存在性能问题......所以我想知道共享指针是否会导致性能下降。如果是这样,那有多难?非常感谢。

我的程序是多线程的,使用 std::tr1::shared_ptr

4

7 回答 7

13

如果您的应用程序传递大约 700 字节的 XML 消息,这些消息可能包含在 65 字节的 Google 协议消息或 85 字节的 ASN.1 消息中,那么这可能无关紧要。但是,如果它每秒处理一百万个东西,那么我不会忽略在传递指针时添加 2 个完整的读取修改写入 (RMW) 周期的成本。

完整的读取修改写入大约为 50 ns,因此两个为 100 ns。此成本是 lock-inc 和 lock-dec 的成本 - 与 2 个 CAS 相同。这是 Windows 临界区保留和释放的一半。这与单个机器周期推送(2.5GHZ 机器上的 400 PICO 秒)相比

这甚至不包括使实际包含计数的缓存行无效的其他成本、BUS 锁对其他处理器的影响等。

通过 const 引用传递智能指针几乎总是首选。如果被调用者在想要保证或控制被指向者的生命周期时没有创建新的共享指针,那么这是被调用者中的错误。不顾一切地通过值传递线程安全引用计数智能指针只是要求性能命中。

引用计数指针的使用无疑简化了生命周期,但是通过值传递共享指针以试图防止被调用者中的缺陷是完全无稽之谈。

过度使用引用计数可以在短时间内将可以处理每秒 1 毫米消息 (mps) 的苗条程序变成在同一硬件上处理 150k mps 的胖程序。突然之间,您需要半个机架的服务器和 10000 美元/年的电费。

如果您可以在没有引用计数的情况下管理对象的生命周期,那么您总是会更好。

一个简单改进的例子是,如果你要扇出一个对象,并且你知道扇出的宽度(比如 n)增量为 n,而不是在每个扇出处单独递增。

顺便说一句,当 cpu 看到一个锁定前缀时,它确实会说“哦,不,这会很痛”。

话虽如此,我同意每个人的观点,即您应该验证热点。

于 2009-10-13T05:02:01.500 回答
9

鉴于数据,几乎不可能正确回答这个问题。真正了解导致应用程序性能问题的唯一方法是在程序上运行分析器并检查输出。

话虽如此,shared_ptr 不太可能导致速度变慢。shared_ptr 类型和许多早期的本土变体被用于越来越多的 C++ 程序中。我自己在工作中使用它们(在家专业)。我花了很多时间来分析我的工作应用程序,而 shared_ptr 从来没有接近我的代码或应用程序中运行的任何其他代码中的问题。错误更有可能在其他地方。

于 2009-10-12T18:02:12.463 回答
5

如果您的程序似乎存在性能问题,那么开始猜测问题可能是非常自然的,但如果您想下注,则几乎 100% 可能完全是其他问题。分析可能会发现问题。这是我使用的方法。

于 2009-10-12T20:13:33.540 回答
5

共享指针是引用计数的。特别是当您使用多线程时,增加和减少引用计数可能会花费大量时间。多线程在这里受到伤害的原因是,如果您在线程之间传递共享指针,引用计数最终将在这些线程之间共享,因此任何操作都必须在线程之间同步。这会使事情变慢很多。

编辑:对于那些关心线程互锁有多慢可以进行一些相当简单的操作的人,请参阅 Herb Sutter 对CoW Strings的一些实现进行的测试。虽然他的测试远非完美(例如,他只在 Windows 上测试过),但它仍然提供了一些关于您可以预期的减速类型的想法。对于大多数实际目的,您可以/可以将 CoW 字符串视为类似 a 的东西shared_ptr<charT>,并添加了许多(不相关的)成员函数。

于 2009-10-12T18:01:59.843 回答
3

非常不可能——你必须花费大部分时间来传递指针。

共享 ptrs 的影响通常很小,甚至很难构造一个它们成为问题的边缘情况(假设一个正确的实现和一个正确优化的编译器)。

共享 ptr 的影响:

  • 增加分配大小。
    仅当您有许多指向非常小的对象(例如,数千万个shared_ptr<int>)的共享指针和/或接近内存限制时,这才有意义。如果额外分配超过内部循环中的缓存/NUMA 级别,则性能显着下降的可能性很小

  • 增加的分配
    shared_ptr数量会覆盖一个跟踪对象(引用计数、弱计数和删除器)。如果分配和释放的总数很高,这会给堆带来压力,并且可能会导致总体减速。
    可以通过使用make_shared、将引用对象和跟踪对象放入单个分配中来避免

  • 引用计数
    增加了指针副本的成本。在单线程应用程序中,您会注意到,无论如何,只有你们中的大部分时间都在复制指针。在多线程应用程序中,您仍然需要对相同的指针进行高争用。通过传递例如作为函数参数
    ,可以在许多地方避免复制成本。shared_ptr<T> const &

  • 取消引用
    在优化编译器的发布版本中,额外的取消引用成本为零。调试构建通常等同于函数调用和额外的 NULL 检查。尽管如此,尤其是在调试版本中,您必须花费大部分时间取消引用指针才能发挥作用。


如果没有其他信息,我们将无法为您提供帮助。您需要描述“性能问题”是什么(一般缓慢、某些操作需要很长时间、大量交换)以及一些关键数据——您的应用程序做了什么,有多少智能指针,它们被复制的频率,以及您在处理智能指针之外还运行了哪些其他操作。

或者您学习使用性能监视器和/或分析器来找出导致减速的原因以及是否存在特定的瓶颈。

于 2009-10-12T18:36:27.120 回答
1

可能会损害性能的一件事是过度传递 shared_ptr 作为函数参数。一个解决方案是传递对 shared_ptr 的引用。但是,这是微优化,因此仅在真正需要时才进行

编辑:考虑到这一点,有更好的优化方法:

  • 当过度传递指针时,您可能应该让对象做一些事情而不是拖动它。
  • 您可以将(const)引用传递给对象而不是指针
  • 在需要更改指针时传递对指针的引用
于 2009-10-12T20:25:39.053 回答
0

不要猜测性能:分析您的代码。

于 2009-10-12T21:05:25.977 回答