執行人: Wufangni
專題解說影片
ChenFuhuangKye
建議不要使用圖片說明,可以試著使用 graphviz 。
謝謝提醒,已更正。
SuNsHiNe-75
提到 Augmented Red-Black Tree 會將樹的額外資訊「藏」在節點中,其具體手法為何?
這些資訊的保留是否會影響到此資料結構的空間複雜度?
Augmented Red-Black Tree 可以對節點附加額外資訊,並採用各種維護操作去更新增強過後的節點內容,其原理為在原本定義節點的資料結構中新增要附加的額外內容,下面以子樹數目做為附加資訊的例子:
且 Augmented Red-Black Tree 在空間複雜度造成的影響主要取決於要附加的內容,以上面例子來說若在每個節點都新增一個整數空間
size
,則最後所增加的空間為 ,雖然增加了附加資訊但整體的空間複雜度仍為 。
lintin528
請問在 最大節點數達到 1000 時,分別對插入搜尋刪除三種操作時長的紀錄
中,在 Node Count
為 150-250
的範圍內時,為何會出現浮動,這是隨機事件還是在每次的統計中都會發生呢?
產生浮動的原因可能在於每次操作的細微差異造成不穩定的曲線狀態,可從不同迭代次數的實驗中(在 TODO: 設計實驗 中提及)看出,若增加每次紀錄的操作次數再取平均可得到較為平穩的分佈曲線。
fennecJ
針對 linux kernel 中紅黑樹的相關函式 API 進行廣泛的探討,閱讀文章過程中我學到了許多,也複習了不熟悉的紅黑樹操作流程。
問題一
「紅黑樹在重新平衡的標準下容忍度相較高,減少需重新平衡的時間成本。」可否進一步說明,或透過分析其演算法呈現為何其容忍度較高。
紅黑樹遵守五大原則(在 TODO: 研究 Linux 核心的紅黑樹 中提及),因此做插入或刪除操作時只會因為黑色節點數量不一致或顏色規則而需重新平衡,而 AVL Tree 在平衡結構方面規劃的較為嚴謹,若左右子樹差超過 1 時就須重新平衡,因此整體所需的重平衡成本相較高於紅黑樹。
問題二
針對運用場景的歸納,為何 AVL tree 相較 rb tree 更適合用於搜尋較多的場合
因上述回答說明,AVL Tree 在平衡方面有較嚴謹的規範,因此整體平衡性會高於紅黑樹,因此在搜尋操作下相較於紅黑樹能更有效率的找出目標節點。
問題三
rb_augmented_tree 中使用了 propagate function 進行資料的維護,該 function 的時間複雜度為何?是否會影響到原本 insert/del 的時間複雜度。
根據紅黑樹性質其樹高最多被限制在 ,而
propagate
用於向上更新節點附加資訊(從更新節點位置到至多根節點位置),因此時間複雜度為 ,且由於額外維護操作與樹高成正比,因此在插入及刪除操作下時間複雜度仍為 。
研究 Linux 核心紅黑樹的實作手法,並予以量化分析。
將 rbtree 自 Linux 核心原始程式碼搬到使用者空間,並與第四周測驗
3
提到的 XTree (注意: 這名稱沒有特別意義,更沒有論文發表,純粹是給學員練習改寫的程式碼) 比較,探討其設計考量。
可對照 Linux 核心專題: 紅黑樹實作的成果,善用 rb-bench 程式來量化程式表現。
圖例應以 Graphviz 或其他向量繪圖描述語言來處理Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
紅黑樹 (red-black tree,在 Linux 核心簡稱 rbtree) 做為一種自我平衡樹在新增、刪除或搜尋等操作下時間複雜度均為 ,特別的地方在於樹高採用 black height (不含自身節點到樹葉節點的總黑色節點數量),相較於 AVL Tree 嚴格要求每個節點左右子樹高度差不超過 1,紅黑樹在重新平衡的標準下容忍度相較高,減少需重新平衡的時間成本。
因此以不同角度對 AVL Tree 與紅黑樹做觀察和比較,前者雖然整體結構較平衡,但需要比多的平衡時間,而在搜尋資料方面 AVL Tree 因較好的平衡規範表現優於紅黑樹,所需空間方面紅黑樹只需 1 個位元的變數來區分紅色或黑色資訊,因此可將兩種資料結構的運用場景歸納如下:
列出各式的屬性,特別是數學證明
紅黑樹樹高證明
假設 rbtree 有 個節點,且樹高用 、每一條路徑(從根結點至樹葉節點)的黑色節點數量用 表示:
根據紅黑樹原則之一「不能出現連續紅色節點」,因此每一條路徑(從根結點至樹葉節點)的紅色節點必不會多於黑色節點,紅黑樹高度 至少有一半以上為黑色節點,可推論出: 。
且 必小於或等於 數目,因此可合併成: 。
根據完整二元樹公式若樹高為 則節點數為 ,因此最小黑節點樹高為 。
由於 ,因此將上述式子整理成: 。
可推論出紅黑樹樹高 為: 。
原始程式碼:
專題要求學員要在 Linux v6.8+ 的環境驗證,因此探討的原始程式碼也該升級到 Linux v6.8
紅黑樹從 2-3-4 樹變化而來,因此可以自由地在兩結構之間做變換,主要遵守五大原則:
免父權主義的遺毒,本文將 parent node 翻譯為「親代節點」,而非「父節點」或「母節點」,不僅更中性,也符合英文原意。若寫作「父」,則隱含「母」的存在,但以二元樹來說,沒有這樣成對關連性。若用「上代」會造成更多的混淆,在漢語中,「上一代」沒有明確的血緣關係 (例如「炎黃子孫」與其說是血緣關係,不如說是傾向文化認同),但「親」的本意就是指名血緣和姻親關係。
rb_node
結構體存放三種資訊,*rb_left
, *rb_right
分別指向左右子節點位址,__rb_parent_color
存放了親代節點位址,且由於 __attribute__((aligned(sizeof(long)))
會讓編譯器對 sizeof(long)
做對齊,因此最低兩位元不會被使用到 (永遠都是零),可用來儲存該節點顏色資訊,而 rb_root
用來儲存根節點的位址資訊。
注意書寫規範:
好的,已更正
不要急著回應,後面還有一堆要改。
依據 lab0 規範的程式碼風格,縮排是 4 個空白字元,本處列出的程式碼也該跟著變更。Image Not Showing Possible ReasonsLearn More →
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
注意細節!
注意書寫規範:
較常使用的 rb_parent
過濾掉 __rb_parent_color
最低兩位元資訊,用於獲取親代節點的位址。
rb_link_node
透過指定 __rb_parent_color
到 *parent
的方式將 *node
的親代節點位址指定到 *parent
。
探討以下 cache 生效和更新的條件。
rb_root_cached
除了儲存根節點位址外也存了最左節點(樹的最小節點)資訊,因此使用 #define rb_first_cached(root) (root)->rb_leftmost
可直接存取cache儲存的最左節點,搜尋結果的時間複雜度只需 。
注意書寫規範:
對 rb_root_cached
做插入、刪除、替換節點的操作時需要一同更新 cache 內最左節點的資訊。
區分「函數」和「函式」,務必詳閱 https://hackmd.io/@sysprog/it-vocabulary
Augmented Red-Black Tree 可在一般傳統紅黑樹上附加額外的資訊及維護功能,將額外資訊藏在節點中 (比如子樹的最大值、最小值…等),且由於它帶有除了節點自身的額外附加資訊,因此應用在很多重要的場合當中,以下是對它應用場景的舉例:
注意用語:
下方的案例,你真的閱讀相關的原始程式碼並進行判斷嗎?抑或,仍停留在人云亦云的階段?
務必使用本課程教材規範的用語。
Linux 核心設計: 檔案系統概念及實作手法
用對應的原始程式碼來說明 augmented red-black tree 的應用場景。
Linux 核心開發者提出此機制的原因在於 Augmented Red-Black Tree 可添加多樣的額外訊息在節點當中,且具有靈活的回調機制使用者可根據自身要求去對附加資訊定義和修改,在不顯著影響時間複雜度的情況下利用少量操作去維護輔助資訊,使其操作可保持在紅黑樹基本操作下的時間成本 ,同時可有效提升上述提及的不同場合下的使用效能。
探討 Augmented Red-Black Tree 的使用場合。
為何 Linux 核心開發者提出此機制?
從 rbtree_augmented.h
可看到對於 Augmented Red-Black Tree 的相關定義與實作方式,使用 rb_augment_callbacks
做各種維護操作,主要結構如下:
*propagate
代表在插入、刪除節點和調整顏色時用來向上更新子樹的附加資訊*copy
代表節點被替換時複製附加資訊*rotate
代表在旋轉時更新附加資訊rbtree_augmented.h
內的常用定義:
rb_set_parent
將 *rb
節點的顏色與 *p
的位址合起來指定給 rb->__rb_parent_color
,等同於設定 *p
作為 *rb
的親代節點。
__rb_change_child
為替換節點操作,如果親代節點存在則判斷要被替換的節點是否為左子節點,一致就將左子節點位址指定到新節點,否則指定右子節點位址到新節點,若親代節點不存在代表此被替代節點為根節點,則指定根節點為址到新節點。
__rb_erase_augmented
刪除節點情況:
以 Graphviz 或其他向量繪圖描述方式重新製圖。
注意用語!
Case2 如果刪除節點有兩個子節點,且刪除節點的右子節點就是後繼節點:
若 child2 為紅色,後繼節點必為黑色,則刪除節點有兩種情況(圖中白色節點代表可為黑也可為紅):
Case3 如果刪除節點有兩個子節點,且刪除節點的右子節點不是後繼節點:
持續直到找到後繼節點 (succesor) 為止:
將後繼節點往上拉到刪除節點的右子節點:
此時欲替代刪除節點的後繼節點已變成刪除節點的右子節點,與 Case2 情況相同,因此後續交換節點與平衡方式等同於 Case2 作法。
在 lib/rbtree.c
中的基本輔助函數 rb_set_black
與 *rb_red_parent
,前者用於設置輸入節點的顏色為黑色,後者則是獲取紅色節點 *red
的親代節點。
__rb_rotate_set_parents
在旋轉操作中設置親代節點和顏色,old 節點被替換成 new 節點,且更新親代節點指針和顏色。
閱讀《Demystifying the Linux CPU Scheduler》第三章關於 AVL tree vs. rbtree 的討論,注意 CFS 總是存取具備最小 vruntime 的節點,因此 Linux 核心的紅黑樹特別處理 cache (不是 CPU cache,而是著重減少非必要的記憶體存取,當 CFS 總是要取得數值最小的節點時)。
務必使用本課程教材規範的術語,process 譯作「行程」。
在 linux 核心中紅黑樹資料結構應用在任務的排程器當中,系統將可執行的任務儲存在紅黑樹,再以執行時間的線性順序插入到行程中,且因紅黑樹的自平衡搜尋樹特性,樹高會與基本操作(插入、刪除和搜尋)的時間成本成正比,因此在平衡過程中會盡可能減低樹的高度以降低時間成本。
提供關於樹高的數學證明。
紅黑樹三種操作的時間複雜度計算
從前一個 TODO 的樹高證明 來對紅黑樹三種操作的時間成本做推導:
CPU 排程器在選取下一個要執行的任務時會依據 vruntime 最小來做選擇,因此需找出紅黑樹最左邊節點,一般紅黑樹在 n 個節點下樹高 ≤ 2 log2(n + 1),搜尋的時間複雜度為 ,然而在 lib/rbtree.c 中可看到紅黑樹存在有快取和非快取兩種結構的版本,有快取版本中用來儲存根節點位址的結構體 rb_root_cached
會多儲存最左邊節點的資訊,且以多個更新操作去維護他,此時對最小節點的搜尋成本降為 ,可顯著加速搜尋過程,CFS 中採用快取版本來維護最小執行時間的任務。
提供對應的數學證明。
cache 結構下的操作時間複雜度計算
若任務是被阻止的不會再次進入 runqueue,但若是遇到 timeslice 結束或是被其他任務搶先則會插入到更新執行時間後的紅黑樹中,且可能不會在原先的位置(經過 時間的重新平衡)。
以 Graphviz 或其他向量圖形描述方式重新製圖。
以上圖範例來看,左邊以一個黑色子節點代表最淺的樹葉節點,右邊則利用紅黑穿插來增加樹高(不能有連續紅節點情況),且要符合每條路徑內的黑節點數量一致,因此最深可達樹高 3 的樹葉節點,確保了最深樹葉節點的樹高不會超過最淺樹葉節點的兩倍。
提供數學證明
根據上述內容及前置作業對紅黑樹的基本認知,紅黑樹的優點可以分為下面兩點:
注意用語:
提供關於最壞狀況的數學證明。
將紅黑樹與常使用的 AVL tree 做比較,列出了兩結構在時間、空間及使用場景下的差異性,且由於 CFS 需要頻繁的插入和刪除,因此紅黑樹更適合 CFS。
rbtree | AVL tree | |
---|---|---|
平衡性 | 較低,但相較於AVL tree 減少了每次操作需要重新平衡的次數 | 較高,AVL tree 有嚴格規定左右子樹之間的高度不能大於2 |
空間需求 | 較低,只需一位元儲存顏色資訊 | 較高,需要一整個整數變量來儲存平衡因子 |
搜尋效率 | 較低,平衡狀態不如 AVL tree,時間複雜度為 | 較高,屬於高度平衡結構因此時間複雜度為 |
應用場景 | 適用於頻繁添加/刪除節點的場景 | 適用在需密集查詢資料的場景 |
針對上述 rbtree, AVL tree, XTree 等平衡二元樹的使用場景,設計對應的實驗,應涵蓋多種資料分布,對應到真實世界的應用場景。
設計紅黑樹測試內容,引入 rbtree.c 及 rbtree.h 並撰寫測試檔 rbtree-tst.c,首先建立欲操作的紅黑樹節點結構 my_node
,包含一個節點值 key 及紅黑樹存放指針結構 rb_node
。
加入搜尋、插入 my_insert
、刪除等操作,且在插入及刪除節點時使用紅黑樹平衡函式 rb_insert_color
及 rb_erase
,避免紅黑樹規則被破壞。
新增紅黑樹根節點 tree
,使用 clock_t
紀錄操作開始與結束的時間,且定義要紀錄的最大節點數 max_node_count
、每次操作的節點數量 step_size
,以及迭代次數 iterations
,實驗會以多次迭代的平均作為該次操作的計算結果。
區分「函數」和「函式」,前者要符合數學的定義。
新增用來計算操作時長的函式 get_time_in_seconds
。
注意書寫規範:
n 等同於 step_size
,每次操作會採用 rand()
對該次操作的全部節點指定隨機值。
計算插入節點操作
注意用語,參見本課程教材的書寫風格。
建立新的插入節點 *new_node
並指定 key 值,start = clock()
和 end = clock()
分別紀錄操作開始與結束的時間,且使用 get_time_in_seconds
計算總操作時長。
計算搜尋節點操作
與插入操作相同計算方式,分別紀錄開始與結束時間並使用 get_time_in_seconds
取得總操作時長。
計算刪除節點操作
先利用 my_search
搜尋欲刪除的節點,後續計算方式與插入操作相同,分別紀錄開始與結束時間並使用 get_time_in_seconds
取得總操作時長。
最後在每次操作結束後釋放儲存節點值的記憶體 free(keys)
。
完整程式碼在 Wufangni/rbtree
最大節點數增加到1000時可明顯分別出三個操作下時間長度的差異,插入和刪除操作因多次重平衡(旋轉和上色)所需時間會高於搜尋操作。
圖表中最後呈現結果反而在刪除操作時效能會優於搜尋操作,但搜尋時不需要做重新平衡,理應低於刪除操作的執行時長,但只有在初期實驗(最大節點數 50 以內)有呈現這個趨勢,目前還沒找出能解釋的原因。
此外也分別對不同迭代次數做實驗,以下皆為 max_node_count = 250
時不同 iterations
的實驗結果:
iterations = 1
iterations = 5
iterations = 10
iterations = 50
可看到若沒有設置迭代次數(iterations = 1
)只單靠一次操作時長做紀錄,會因每次操作的細微差異造成不穩定的曲線狀態,在三種不同操作下的辨識程度會非常低,難以區分各操作的效能優劣,隨著迭代次數的增加取平均後可看到不同操作分佈逐漸分開,曲線也不會像單一次數那樣呈現震盪,若是迭代次數設置太大(iterations = 50
)則容易造成分佈相似的操作更緊密,差異較大的分的更明顯的現象,對於原本比較接近的刪除和搜尋兩操作會更不易於分辨。
在 rbtree.h
檔案中新增外部宣告 rb_rotation_count
,用於紀錄旋轉次數。
在 rbtree.c
中加入 reset_rb_rotation_count
函式
更改 __rb_rotate_left
和 __rb_rotate_right
,增加 rb_rotation_count++
以計算每次左旋和右旋的次數。
修改 Makefile
根據圖表呈現插入操作的旋轉次數使用最多,其次為刪除操作,驗證了兩操作在時間總長實驗下的差異性(插入所花時間明顯高於刪除),而搜尋操作因沒有直接對樹的本身結構做改變則不須重新平衡,故旋轉次數為零。
cache miss 作為一個新的判斷效能的依據,若在執行過程中產生 miss 則須從記憶體再做一次存取的操作,造成效能降低,因此使用 perf
來統計 Data Cache Misses 總次數,並對不同操作進行了分析及比較。
新增 measure_cache_misses
函數,建立一個 command 命令用來統計插入、刪除和搜尋操作的 cache 未命中次數。
打開進程來執行剛剛建立的 command,並連接到 perf_output
建立一個儲存 cache 輸出結果的緩衝區,並從 perf_output 讀取一行輸出到 buffer,轉換型態存進 *cache_misses
中,最後關閉剛剛被打開的 perf_output。
在插入刪除,及搜尋操作下呼叫 measure_cache_misses
函數並以不同變數累加該次的 cache miss,最後除以迭代次數取的平均值。
注意書寫規範:
可看到在插入操作時的 miss 情況會比較多,與執行所花時長呈現正相關,表示插入操作執行過程中會比另兩個操作多花費了大量重新存取資料的時間。
實做 AVL tree 的兩個檔案 avltree.c 及 avltree.h,並新增 avlVSrb-tst.c 測試檔,引入兩種資料結構和分別紀錄在插入刪除和搜尋三個操作下的執行時間。
根據兩資料結構的性質,rbtree 較適合進行大量插入刪除的操作,而在實驗結果中可看到在插入操作過程中 rbtree 的執行時間明顯低於 AVL tree,而在刪除與搜尋兩操作執行時間差異不大,較難對此做區分。
你該改善測驗題裡頭的 XTree,而非現有論文的 XTree。
錯把馮京當馬涼