boost 智慧型指標 關於效能的少數派報告

2021-08-22 01:33:23 字數 3945 閱讀 1648

開門見山好了,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()

void add_ref_lock()

其中 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()

bool add_ref_lock() // true on success

}上面的 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()

clk_e = std::clock();

std::cout << clk_e - clk_s << std::endl;

}std::cout << std::endl;

for(int i = 0; i < 10; ++i)

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

可以看到,由於在單執行緒環境下,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( ;; )

後者帶來的執行時間的增加平均達到 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()

以及bool add_ref_lock() // true on success

#else

if(use_count_ == 0) return false;

++use_count_;

#endif

}就可以保證在兩種情況下的最優性能。

boost 智慧型指標

boost shared ptr 的記憶體管理機制 boost shared ptr 的管理機制其實並不複雜,就是對所管理的物件進行了引用計數,當新增乙個 boost shared ptr 對該物件進行管理時,就將該物件的引用計數加一 減少乙個 boost shared ptr 對該物件進行管理時,...

boost 智慧型指標

最近使用boost的智慧型指標,檢視了一些帖子。總結如下 智慧型指標分類 智慧型指標使用注意事項 多執行緒安全性分析 這個帖子的結論很好,1 shared ptr是乙個非常實用的智慧型指標。2 shared ptr的實現機制是在拷貝構造時使用同乙份引用計數。3 對同乙個shared ptr的寫操作不...

boost 智慧型指標

shared 指標類似於乙個帶計數器的指標,當指標計數次數為0時,它將自動析構物件。shared ptr指標可通過一次new出來,一直向下傳遞,直到每次析構shared ptr時將該指標引用量 1 注 每次進行不帶引用的傳遞時呼叫次數會加1,但析構時同時會 1,若用引用傳遞,每次呼叫值不增加,但同樣...