簡體   English   中英

C ++ weak_ptr創建性能

[英]C++ weak_ptr creation performance

我已經讀過創建或復制std :: shared_ptr涉及一些開銷(引用計數器的原子增量等)。

但是如何從它創建一個std :: weak_ptr:

Obj * obj = new Obj();
// fast
Obj * o = obj;
// slow
std::shared_ptr<Obj> a(o);
// slow
std::shared_ptr<Obj> b(a);
// slow ?
std::weak_ptr<Obj> c(b);

我希望在一些更快的性能,但我知道共享指針仍然必須遞增弱引用計數器..所以這仍然像將shared_ptr復制到另一個慢?

這是我與游戲引擎的日子

故事如下:

我們需要一個快速的共享指針實現,不會破壞緩存(緩存現在更聰明btw)

正常指針:

XXXXXXXXXXXX....
^--pointer to data

我們的共享指針:

iiiiXXXXXXXXXXXXXXXXX...
^   ^---pointer stored in shared pointer
|
+---the start of the allocation, the allocation is sizeof(unsigned int)+sizeof(T)

用於計數的unsigned int*位於((unsigned int*)ptr)-1

這樣一個“共享指針”是指針大小,它包含的數據是指向實際數據的指針。 所以(因為template => inline而且任何編譯器都會內聯一個運算符返回一個數據成員)它與普通指針的訪問權限相同。

創建指針需要比正常情況多3個CPU指令(訪問位置-4正在運行,添加1和寫入位置-4)

現在我們在調試時只使用弱指針(因此我們使用DEBUG定義(宏定義)進行編譯)因為那時我們希望看到所有的分配和最新情況等等。 這很有用。

弱指針必須知道他們指向的東西何時消失,不要保持他們指向的東西(在我的情況下,如果弱指針保持分配活着,引擎永遠不會回收或釋放任何記憶,那么它基本上是無論如何共享指針)

所以每個弱指針都有一個bool, alive或者什么東西,並且是shared_pointer的朋友

調試我們的分配時看起來像這樣:

vvvvvvvviiiiXXXXXXXXXXXXX.....
^       ^   ^ the pointer we stored (to the data)
|       +that pointer -4 bytes = ref counter
+Initial allocation now 
    sizeof(linked_list<weak_pointer<T>*>)+sizeof(unsigned int)+sizeof(T)

您使用的鏈表結構取決於您關注的內容,我們希望保持盡可能接近sizeof(T)(我們使用伙伴算法管理內存),因此我們存儲了指向weak_pointer的指針並使用了xor技巧。 ... 美好時光。

無論如何:指向shared_pointers指向的東西的弱指針放在一個列表中,以某種方式存儲在上面的“v”中。

當引用計數達到零時,您將瀏覽該列表(這是指向實際weak_pointers的指針列表,當它們顯然被刪除時它們將自行刪除)並且您將alive = false(或其他內容)設置為每個weak_pointer。

weak_pointers現在知道他們指向的東西不再存在(所以在被引用時扔掉了)

在這個例子中

沒有開銷(系統的對齊是4個字節.64位系統往往喜歡8個字節的對齊....在那里將ref-counter與int [2]結合在一起以填充它。記住這個涉及到新聞(沒有人投票,因為我提到它們:P)等等。你需要確保你對分配施加的struct與你分配和制作的struct相匹配。編譯器可以自己對齊東西(因此int [2]不是int, INT)。

您可以完全取消引用shared_pointer,而不需要任何開銷。

正在制作的新共享指針根本不會破壞緩存並且需要3個CPU指令,它們不是很容易管道但是編譯器總是會內聯getter和setter(如果不是總是:P)那么'將成為可以填充管道的呼叫站點周圍的東西。

共享指針的析構函數也很少(遞減,就是這樣),所以很棒!

高性能筆記

如果你有這樣的情況:

f() {
   shared_pointer<T> ptr;
   g(ptr);
}

無法保證優化器不敢通過“按值”將shared_pointer傳遞給g來進行加法和減法。

這是您使用普通引用(實現為指針)的地方

所以你要做g(ptr.extract_reference()); 相反 - 編譯器將再次內聯簡單的getter。

現在你有一個T&,因為ptr的范圍完全包圍g(假設g沒有副作用等等),該引用在g的持續時間內有效。

刪除引用是非常難看的,你可能不會偶然做到(我們依賴這個事實)。

事后來看

我本應該創建一個名為“extracted_pointer”的類型,或者很難為類成員輸入錯誤的類型。

stdlib ++使用的弱/共享指針

http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html

不是那么快......

但是不要擔心奇怪的緩存未命中,除非你制作一個游戲引擎沒有運行不錯的工作量> 120fps:P仍然比Java更好。

stdlib方式更好。 每個對象都有自己的分配和工作。 使用我們的shared_pointer這是一個真實的例子,“相信我的工作,不要擔心如何”(不是很難),因為代碼看起來非常混亂。

如果你解除了......他們對他們實現中的變量名稱做了什么,它會更容易閱讀。 參見Boost的實現,正如文檔中所述。

除了變量名之外,GCC stdlib實現很可愛。 你可以很容易地閱讀它,它可以正常工作(遵循OO原則)但速度稍慢,並且可能會在最近蹩腳的芯片上破壞緩存。

UBER高性能筆記

您可能在想,為什么不擁有XXXX...XXXXiiii (最后的引用計數)然后您將得到最好的分配器對齊!

回答:

因為必須做pointer+sizeof(T)可能不是一個CPU指令! (減去4或8是CPU可以輕松完成的事情,因為它有意義,它會做很多事情)

除了Alec對他之前項目中使用的shared / weak_ptr系統非常有趣的描述之外,我還想詳細介紹一下典型的std::shared_ptr/weak_ptr實現可能發生的事情:

// slow
std::shared_ptr<Obj> a(o);

上述結構的主要費用是分配一塊內存來保存兩個引用計數。 這里不需要進行原子操作(除了在operator new下執行可能會或可能不會執行的operator new )。

// slow
std::shared_ptr<Obj> b(a);

復制構造中的主要開銷通常是單個原子增量。

// slow ?
std::weak_ptr<Obj> c(b);

這個weak_ptr構造函數的主要開銷通常是單個原子增量。 我希望這個構造函數的性能幾乎與shared_ptr復制構造函數的性能相同。

另外兩個要注意的重要構造函數是:

std::shared_ptr<Obj> d(std::move(a));  // shared_ptr(shared_ptr&&);
std::weak_ptr<Obj> e(std::move( c ));  // weak_ptr(weak_ptr&&);

(以及匹配的移動賦值運算符)

移動構造函數根本不需要任何原子操作。 他們只是將引用計數從rhs復制到lhs,並使rhs == nullptr。

僅當賦值之前的lhs!= nullptr時,移動賦值運算符才需要原子遞減。 大部分時間(例如在vector<shared_ptr<T>> )移動賦值之前的lhs == nullptr,因此根本沒有原子操作。

后者( weak_ptr移動成員)實際上不是C ++ 11,而是由LWG 2315處理。 但是我希望它已經被大多數實現實現(我知道它已經在libc ++中實現)。

當在容器中搜索智能指針時,例如在vector<shared_ptr<T>>::insert/erase ,將使用這些移動成員,並且與使用智能指針復制成員相比,可以產生可測量的積極影響。

我指出它,以便你知道如果你有機會移動而不是復制一個shared_ptr/weak_ptr ,那么輸入一些額外的字符是值得的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM