簡體   English   中英

將鏈表嵌入數據結構有什么好處?

[英]What is the advantage of embedding a linked list into a data structure?

在 FreeBSD 中閱讀 kernel 數據結構時,我偶然發現了MBuf MBuf包含一個指向MBuf鏈中下一個MBuf的指針,實現了一個鏈表。 每個MBuf本身還包含特定於鏈表中該節點的數據。

我更熟悉將容器類型與值類型分開的設計(考慮std::listSystem.Collections.Generic.LinkedList )。 我正在努力理解將容器語義嵌入到數據類型中的價值主張——獲得了哪些效率? 真的只是為了消除節點實例指針存儲嗎?

考慮您在列表中有一個節點的迭代器/指針。 為了獲取數據,您必須:

  • 從節點讀取指向數據的指針
  • 解引用剛剛讀取的指針並讀取實際數據

另一方面,如果列表概念“嵌入”在您的數據結構中,則您可以在對象與節點本身一起的單個存儲操作中讀取對象。

分離的列表節點及其數據的另一個問題是列表節點本身很小(通常只有2或3個指針)。 結果,在存儲器中保持如此小的結構的存儲器開銷可能很重要。 您知道-諸如newmalloc實際上消耗的內存要比它們分配的更多-系統使用其自己的樹結構來跟蹤內存的可用空間和空閑空間。

在這種情況下,將事物分組為單個分配操作是有益的。 您可以嘗試將幾個列表節點放在小束中,也可以嘗試將每個節點與其分配的數據連接起來。

使用侵入性指針(相對於共享指針)或將對象和智能指針數據打包在一起的std::make_shared可以看到類似的策略。


zett42發表評論說std::list<T>T與節點數據保持在一起。 如上所述,這實現了單個存儲塊,但有一個不同的問題: T不能是多態的。 如果您具有類A及其派生類B ,則node<B>不是node<A>的派生類。 如果您嘗試將B插入std::list<A> ,則您的對象將:

  • 在最佳情況下,導致編譯錯誤(無構造函數A::A(const B&)
  • 在最壞的情況下, 切片 B靜默地將B表示A的一部分復制到節點中。

如果你想在一個列表來保存多態對象的典型解決方案是真正有std::list<A*>代替std::list<A> 但是,最后您得到了我上面解釋的額外間接訪問。

另一種方法是制作一個侵入式列表(例如boost::intrusive::list ),其中節點信息實際上是A對象的一部分。 這樣,每個節點都可以毫無問題地成為A的導數。

侵入式鏈接列表的一大優點是,您可以創建一個預先存在的對象列表,而無需任何新分配。 為此,使用std :: lists指針將需要分配內存。

Boost具有侵入式列表實現,並帶有使用理由。 http://www.boost.org/doc/libs/1_63_0/doc/html/intrusive.html

獲得了什么效率? 真的就是要消除節點實例指針存儲嗎?

我會說更少的緩存未命中,然后會提高整體性能(即使鏈表通常不緩存友好的數據結構)。
這樣,您不必再跟隨一個指針就可以在內存中的某個地方找到數據,並將它們帶到每個節點的處理器附近。
此外,如果您在內存的連續區域中構建節點並使用幾個指針來管理它們(我們稱它們為自由列表和使用中列表,這聽起來很熟悉嗎?),則可以在性能(至少在列表中不包含很多項目的情況下,否則風險是在內存中來回跳動)。 在這種情況下,in和delete的時間是恆定的(當然,除非必須先在列表中搜索一個節點,然后才能插入特定位置),否則這是另一個優點。

侵入式列表的主要優點之一是您可以廉價地讓單個節點屬於多個列表。

例如,您可以擁有以 3 種不同方式排序的項目集合,對應於 3 個不同列表中的條目。 例如,用std::list做這件事會很笨拙。

正如@doron 所提到的,我認為的另一大優勢是一旦創建了對象,列表管理就需要 0 次分配。

Boost 對侵入式與非侵入式數據結構進行了一些不錯的討論,各有優缺點。

暫無
暫無

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

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