Skip to content

Latest commit

 

History

History
99 lines (86 loc) · 6.17 KB

File metadata and controls

99 lines (86 loc) · 6.17 KB

std::shared_ptr vs. boost::shared_ptr 性能测试

测试std::shared_ptr<T>boost::shared_ptr<T>在不同场景下的性能表现.

实验环境

  • Linux:
    • Ubuntu 18.04 LTS(WSL(Windows Subsystem for Linux) on Win10 1803-17134.228)
    • cmake version 3.10.2
    • gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
    • GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
    • Boost version: 1.65.1

实验过程

Release编译, 并关闭编译器优化(-O0)

$ mkdir build
$ cd build
$ cmake ../            
$ cmake --build .
$
$ ./test_std_shared_ptr 
test_case_pass_shared_ptr ---> 0.633972 seconds, 0.353455 seconds, 0.62363 seconds.
test_case_access_value ---> 0.0417496 seconds, 0.11343 seconds
$
$ ./test_boost_shared_ptr 
test_case_pass_shared_ptr ---> 0.412738 seconds, 0.227477 seconds, 0.430141 seconds.
test_case_access_value ---> 0.0414274 seconds, 0.0603911 seconds
$
  • 数据解释
test_case_pass_shared_ptr ---> {(a) 传值方式传递shared_ptr} seconds, 
                               {(b) 传引用方式传递shared_ptr} seconds, 
                               {(c) 传引用方式传递shared_ptr, 但其指向的类型由 T 传递给了 const T } seconds.
test_case_access_value ---> {(d) 通过原生指针直接访问对象内容} seconds, {(e) 通过shared_ptr访问对象内容} seconds
  • test_case_pass_shared_ptr

    • 对于这个test case来说, 不论是std::shared_ptr还是boost::shared_ptr, 表现出的趋势都是(a)和(c)的值相对很接近, (b)的值约为一半的值. 如下分析:
      • (a)中每次循环存在两次shared_ptr的拷贝, 一次是pass by value给构造函数, 一次是构造函数中通过函数参数去构造对象中的成员变量shared_ptr;
      • (b)中传递const shared_ptr<T> &不需要额外的一次shared_ptr拷贝. 所以这里仅一次shared_ptr的拷贝, 即在构造函数中通过函数参数构造对象中的成员变量shared_ptr时;
      • (c)中传递const shared_ptr<const T> &, 虽然是引用传递, 但由于外部调用时原来的shared_ptr类型为shared_ptr<T>, 其并不能赋值给shared_ptr<const T>, 所以这里先要存在一次shared_ptr的拷贝构造(可参考 Difference between const shared_ptr<T> and shared_ptr<const T>? ); 然后另一次同上也是在构造函数中通过函数参数构造成员变量shared_ptr时; 共2次拷贝.
      • 所以, 会出现测试数据中的约2:1:2的结果.
    • 对于为什么shared_ptr的拷贝开销这么大的问题
      • 首先, shared_ptr并不仅仅是一个简单的4字节或8字节的指针, 而是一个object;
      • 既然是object, 那么拷贝就涉及到构造和析构的开销. 更主要的应当是shared_ptr内部的引用计数的维护开销(原子操作), 在频繁操作时会显得很大. 而通过const shared_ptr<T> &来传递, 即显式地不用去触发引用计数的维护, 从而能够明显地降低开销.
  • test_case_access_value

    • 理论上来讲, 通过shared_ptr来访问对象的内容和通过原生指针来访问对象的内容, 并没有什么不同. 最多也就是前者多了一步函数调用, 在shared_ptroperator->()中先调用内部函数获得原生指针, 不应当有如此大的差距.

Release编译, 并打开编译器优化(-O3)

$ # 首先修改 CMakeLists.txt 中的 -O0 ==> -O3 
$
$ mkdir build
$ cd build
$ cmake ../            
$ cmake --build .
$
$ ./test_std_shared_ptr 
test_case_pass_shared_ptr ---> 0.0133012 seconds, 0.002437 seconds, 0.0129323 seconds.
test_case_access_value ---> 8e-07 seconds, 5e-07 seconds
$
$ ./test_boost_shared_ptr 
test_case_pass_shared_ptr ---> 0.166111 seconds, 0.0827962 seconds, 0.164819 seconds.
test_case_access_value ---> 8e-07 seconds, 0.0022888 seconds
$
  • test_case_pass_shared_ptr

    • 即使打开了编译器优化之后, 约2:1:2的趋势仍然比较明显, 尤其是boost::shared_ptr.
    • std::shared_ptr优化后的拷贝开销明显比boost::shared_ptr少了一个数量级;
    • std::shared_ptrpass by reference优化后的效率也明显很高;
    • 从绝对值上来看, 编译器优化的作用还是非常明显的, 尤其是针对std::shared_ptr, 开销直接降低了1~2个数量级.
  • test_case_access_value

    • 经过编译器优化之后, 这个测试数据基本上可以忽略不计了.

结论

  • 在需要频繁传递的场景下, shared_ptr还是会存在比较明显的性能问题. 在性能资源比较紧张时需要重视.
    • 发现这个问题是在QNX 6.5.0平台下, 因为一处shared_ptr频繁拷贝传递导致了整体系统的性能下降约10%.
  • 在我的测试环境下, 编译器优化的作用非常明显, 并且std::shared_ptr的性能也明显好于boost::shared_ptr.
  • 不同平台/系统的表现可能不同, 必要时应实际平台上测试后再得出对应的结论.

References