boost 智能指针 --- 关于性能的少数派报告

news/2023/12/1 8:06:50
开门见山好了,boost 1.33 对于 boost 1.32 的 shared_ptr 和 weak_ptr 有一个不小的改变,然而这个改变如此透明,以至于它甚至于没有出现在 boost  1.33 的 release notes 中。

在 1.32 中,shared_ptr 和 weak_ptr 的引用计数使用锁来保证线程安全,下面一段摘自 boost 1.32 的 sp_counted_base 实现,大家可以在 boost 的 detail/shared_count.hpp 中找到它:

    void add_ref_copy()
    {
#if defined(BOOST_HAS_THREADS)
        mutex_type::scoped_lock lock(mtx_);
#endif
        ++use_count_;
    }

    void add_ref_lock()
    {
#if defined(BOOST_HAS_THREADS)
        mutex_type::scoped_lock lock(mtx_);
#endif
        if(use_count_ == 0) boost::throw_exception(boost::bad_weak_ptr());
        ++use_count_;
    }

其中 add_ref_copy 是用于shared_count ,后者是用于 shared_ptr 的引用计数器;add_ref_lock 用于 weak_count ,后者是用于 weak_ptr 的引用计数器。

可以看到,在多线程环境中,boost 1.32 用 scoped_ptr 来保证引用计数器访问的串行性。但是在 boost 1.33 中,情况却大大不同了,它使用的是 lock free 的算法,在 Win32 平台下面就是通过操作系统的 InterlockedIncrement / InterlockedDecrement 以及 InterlockedCompareExchange 来完成。下面的代码来自 boost 1.33 的 sp_counted_base 实现,位于 boost 的 detail/sp_counted_base_w32.hpp 文件中:

    void add_ref_copy()
    {
        BOOST_INTERLOCKED_INCREMENT( &use_count_ );
    }

    bool add_ref_lock() // true on success
    {
        for( ;; )
        {
            long tmp = static_cast< long const volatile& >( use_count_ );
            if( tmp == 0 ) return false;
            if( BOOST_INTERLOCKED_COMPARE_EXCHANGE( &use_count_, tmp + 1, tmp ) == tmp ) return true;
        }
    }

上面的 BOOST_INTERLOCKED_INCREMENT 和 BOOST_INTERLOCKED_COMPARE_EXCHANGE 都位于 boost 的 detail/interlocked.hpp 文件中,如大家所料,非常简单:

# define BOOST_INTERLOCKED_INCREMENT InterlockedIncrement
# define BOOST_INTERLOCKED_DECREMENT InterlockedDecrement
# define BOOST_INTERLOCKED_COMPARE_EXCHANGE InterlockedCompareExchange

这种变化意味着什么?最起码,lock free 算法为 boost 智能指针带来了非锁定语义。同时,由于 InterlockedCompareExchange 是一个 CAS (Compare And Swap) 操作,它可能由于读 - 写操作之间有其它线程介入而导致失败,为了能从这种失败中恢复过来, boost 1.33 代码中使用了那个 for(;;) 循环,也就是无限的重试以保证引用计数操作成功。在性能方面,由于 Interlocked 系列的系统调用都利用了硬件的原子操作,在性能上应该
比 scoped_lock 有一定的提高,但是循环所引入的额外操作又对性能有不良影响,究竟孰轻孰重,我们下面就在测试中检验。

测试程序非常简单,我尽量不引入无关的操作以免影响结果:
(代码文件:sp.cpp)

#include
#include
#include
#include

using namespace boost;

int main()
{
  clock_t clk_s, clk_e; 
 
  shared_ptr fp(new float(3.14f));
  for(int i = 0; i < 10; ++i)
  {
    clk_s = std::clock();
    for(int j = 0; j < 1000000; ++j)
    {
      shared_ptr fp1 = fp;
    }
    clk_e = std::clock();
   
    std::cout << clk_e - clk_s << std::endl;
  }
 
  std::cout << std::endl;
 
  for(int i = 0; i < 10; ++i)
  {
    clk_s = std::clock();
    for(int j = 0; j < 1000000; ++j)
    {
      weak_ptr wp1 = fp;
    }
    clk_e = std::clock();
   
    std::cout << clk_e - clk_s << std::endl;
  } 
}

我在同一台机器的两套环境下编译这个程序,这两套环境唯一的不同只在于一个使用 boost 1.32 ,一个使用 boost 1.33。编译命令是最简单的一条:

cl /EHsc sp.cpp

注意,这里没有用 /MT 开关,意味着我是在单线程状态下编译,后面会用另外的指令编译,比较其结果是饶有趣味的。

  boost 1.32
 boost 1.33
 shared_ptr (10次,以空格分开)
 150 60 50 60 50 60 50 60 70 60 220 80 90 80 80 90 81 80 90 90
 weak_ptr (10次,以空格分开)
 51 50 60 50 60 50 50 60 50 50 100 90 80 80 91 80 80 90 80 90

我知道一幅图抵得上一千个字,好在用 Excel 做个图也很轻易,下面是这些数据的图表显示:





可以看到,由于在单线程环境下,boost 1.32 的 shared_ptr 引用计数器默认操作实际上是

++use_count_;

而 boost 1.33 则实际上是

InterlockedIncrement( &user_count_ );

后者带来了平均 46.4% 的运行时间延长。对于 weak_ptr ,boost 1.32 中的操作是

if(use_count_ == 0) boost::throw_exception(boost::bad_weak_ptr());
++use_count_;

而 boost 1.33 的操作是

for( ;; )
{
   long tmp = static_cast< long const volatile& >( use_count_ );
   if( tmp == 0 ) return false;
   if( InterlockedCompareExchange( &use_count_, tmp + 1, tmp ) == tmp ) return true;
}

后者带来的运行时间的增加平均达到 62.1% 。这的确是一个值得注意的问题。

接下来我换用另外的编译命令:

cl /EHsc /D"BOOST_HAS_THREADS" sp.cpp

这仍然是单线程,但是我通过定义 BOOST_HAS_TREADS 宏强制 boost 1.32 使用 scope_lock ,以模拟其在多线程下的表现,同时排除其他因素的干扰。


  boost 1.32
 boost 1.33
 shared_ptr (10次,以空格分开)
 380 190 211 190 200 190 201 190 200 191 230 90 80 90 80 90 81 100 80 90
 weak_ptr (10次,以空格分开)
 190 190 190 191 180 190 191 190 190 200 80 90 80 80 91 80 90 80 90 80

当然,还有图:





我们看到,定义 BOOST_HAS_TREADS 宏对于 boost 1.32 的智能指针性能影响是巨大的,而对于 boost 1.33 ,考虑到测量误差,可以说是没有影响。因为在这种情况下,boost 1.33 的引用计数操作没有变化,而 boost 1.32 的 shared_ptr 引用计数变成了

mutex_type::scoped_lock lock(mtx_);
++use_count_;

而 weak_ptr 的引用计数变成了

mutex_type::scoped_lock lock(mtx_);
if(use_count_ == 0) boost::throw_exception(boost::bad_weak_ptr());
++use_count_;

也就是至少多出了一个 scoped_lock 的构造和析构成本,比起 boost 1.33 ,这个时候的 boost 1.32 shared_ptr 平均运行时间多出了约 112% ,weak_ptr 的平均运行时间多出了约 126.2% !我们终于看到了新的实现在性能上的好处。

结论:

新的 lock free 算法使得多线程环境下的 boost 智能指针性能大大提高,但是也带来了单线程环境下的一些性能成本,有没有办法两全其美呢?其实很简单,只要沿用过去的做法,用宏来区别即可:

    void add_ref_copy()
    {
#if defined(BOOST_HAS_THREADS)
        BOOST_INTERLOCKED_INCREMENT( &use_count_ );
#else
        ++use_count_;
#endif

  }

以及

    bool add_ref_lock() // true on success
    {
#if defined(BOOST_HAS_THREADS)
        for( ;; )
        {
            long tmp = static_cast< long const volatile& >( use_count_ );
            if( tmp == 0 ) return false;
            if( BOOST_INTERLOCKED_COMPARE_EXCHANGE( &use_count_, tmp + 1, tmp ) == tmp ) return true;
        }
#else
        if(use_count_ == 0) return false;
        ++use_count_;
#endif
  }

就可以保证在两种情况下的最优性能。





http://www.niftyadmin.cn/n/3655048.html

相关文章

regex_test.cpp -- learning boost.regex

boost.regex 库的用法&#xff0c;看来这可能是 boost 当中写法最“常规”的库之一了。regex_test.cpp:#include #include #include #include #include using namespace std;// purpose:// takes the contents of a file in the form of a string// and searches for all the C…

用OKHTTP框架做服务器通信

人生中第一次写博客&#xff0c;也就是大学大二期间。我认为记录这些点点滴滴的经验和知识有两点必要。第一&#xff0c;是能够记录自己在学习IT之路的一些经验和知识的整理&#xff1b;第二&#xff0c;把自己所理解的知识点更能详细分享给喜欢编程的每一个人&#xff0c;希望…

Android通过OkHttp框架上传照片到服务器

第一次做这种拍照并上传服务器的项目&#xff0c;遇到了很多问题&#xff0c;查了又查&#xff0c;问了又问&#xff0c;之前运用流的方式上传服务器&#xff0c;但是由于忘记增加读取Android的SD卡权限了&#xff0c;以失败告终&#xff0c;最后通过OkHttp框架上传 &#xff0…

搭建Android本地数据库(SQLite)的详细讲解

大家好&#xff0c;今天我给大家整理了一些关于Android本地数据库SQLite搭建的详细步骤&#xff01; SQLite是一款轻型的数据库&#xff0c;它的设计目标是嵌入式的&#xff0c;而且目前已经在很多嵌入式产品中使用了它&#xff0c;它占用资源非常的低&#xff0c;在嵌入式设备…

Android默认记住登陆用户名和密码

每个Android开发者都会遇到一个问题就是在App的登陆界面会有记住密码和记住用户名的复选框按钮&#xff0c;是怎么实现的呢&#xff1f;下面进行展示一下操作代码&#xff01;我们以最简单的自动默认记住用户名为例子&#xff0c;用户选择复选框自行后期添加就好了&#xff01;…

Android 的生命周期深入剖析

一、Android 的生命周期深入剖析 1、正常情况下生命周期 onCreate : 表示页面&#xff08;Activity&#xff09;的创建。&#xff08;生命周期第一个阶段&#xff09;功能&#xff1a;完成初始化工作&#xff0c;如&#xff1a;加载页面布局资源、初始化数据。onStart : 表示页…

What you can do is more important than who your parents are.

在 Kelvin Henney 的 More C Threading 里面看到这句话&#xff0c;发觉它可以作为 GP vs. OO 的绝佳注脚。

C++ Multithreading

许多 C 权威&#xff0c;或者甚至是计算机科学的权威&#xff0c;都把并行&#xff0c;或者在微观的层面上&#xff0c;多线程&#xff0c;看作下一次革命的主题。很久没有关心这些事情了&#xff0c;今天读了一些相关文章&#xff0c;很有启发。首先是 C0x 的一篇 proposal N…

三分钟搭建个人博客 Hexo + Next + github 详细教程

每个程序员必不可少的就是博客网站了&#xff0c;一开始我们并不知道可以搭建属于自己的个人博客网站&#xff0c;通常会在CSDN、博客园等别人搭建的博客网站写博客&#xff0c;当你写久了以后会感觉很 low, 一些文章需要审核通过才能发布。索性我们还不如自己搭建一个个人博客…

boost中的operator及一些探讨

在generic programming中&#xff0c;我们往往希望自己定义的type在行为上和C内置的类型尽可能的相似&#xff0c;也就是说&#xff0c;可以参与各种各样的表达式运算而不会有阻碍。C为我们提供强大的运算符重载机制也就是为了这个目的。不幸的是&#xff0c;重载运算符往往是一…

LeetCode 之 JavaScript 解答第一题 —— 两数之和(Two Sum)

Time&#xff1a;2019/4/1 Title:Two Sum Difficulty: simple Author:小鹿 题目一&#xff1a;Two Sum Given an array of integers, return indices of the two numbers such that they add up to a specific target.You may assume that each input would have exactly one s…

仿照boost::lexical_cast,编写一个text_cast

首先说明&#xff0c;这个text_cast不光是编写来玩的&#xff0c;它还有一定的用途。我在最近的一个跨平台&#xff08;Win32&#xff0c;数个版本的Linux&#xff09;的项目中用到了boost库&#xff0c;编码的时候还是很爽的&#xff0c;等到了移植的时候&#xff0c;就发现我…

LeetCode 之 JavaScript 解答第二题 —— 两数相加(Add Two Numbers)

Time&#xff1a;2019/4/2 Title: ADD Two Numbers Difficulty: medium Author:小鹿 公众号&#xff1a;一个不甘平凡的码农。 题目二&#xff1a;ADD Two Numbers You are given two non-empty linked lists representing two non-negative integers. The digits are stored …

VC7.1 编译器的一个不大不小的bug

这段代码在任何一个成熟的C编译器当中都不会通过&#xff1a;class Test{public: char *p;static void Tt() { p 0; }};因为它在static函数中访问实例成员&#xff0c;而在调用static函数的时候&#xff0c;可能根本就没有实例存在&#xff0c;即便存在&#…

LeetCode 之 JavaScript 解答第十五题 —— 三数之和(3Sum)

Time&#xff1a;2019/4/3 Title:3Sum Difficulty: medium Author:小鹿 题目三&#xff1a;ADD Two Numbers Given an array nums of n integers, are there elements a, b, c in nums such that a b c 0? Find all unique triplets in the array which gives the sum of …

LeetCode 之 JavaScript 解答第169题 —— 求众数 I(Majority Element)

Time&#xff1a;2019/4/4 Title: Majority Element 1 Difficulty: easy Author: 小鹿 题目&#xff1a;Majority Element 1 Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋times. You may as…
最新文章