# 2023q1 Homework4 (quiz4) contributed by< `WeiHongWi` > ## 測驗一 ### Insert ```c for (pathp = path; pathp->node; pathp++) { \ int cmp = pathp->cmp = x_cmp(node, pathp->node); \ assert(cmp != 0); \ if (cmp < 0) { \ pathp[1].node = rbtn_left_get(x_type, x_field, pathp->node); \ } else { \ pathp[1].node = rbtn_right_get(x_type, x_field, pathp->node); \ } } pathp->node = node; ... ``` `path->node` 指向 `rbtree->root`,利用指標 pathp 走訪紅黑樹,比較要插入的新節點內含和紅黑樹已有節點的內含值,則 new node 為當下 pathp 所指的 node 的左子樹 , 若新節點的內含值大於 node ,則 new node 為當下 `pathp` 所指的節點的右子樹. 而迴圈的終結條件為 `pathp->node` 為空, 所以此時 node 就放入 `pathp->node` :::warning :warning: 1. 改進標示,避免濫用 `**`,標點符號採全形。 2. 避免非必要的中英夾雜 3. 善用 ChatGPT 改寫,漢語書寫不該比 OpenAI 差 4. 探討程式碼之前,應簡述個別操作的特性和要點 :notes: jserv ::: ```c for (pathp--; (uintptr_t) pathp >= (uintptr_t) path; pathp--){ ... if (pathp->cmp < 0){ x_type *leftleft = rbtn_left_get(x_type, x_field, left); if (leftleft && rbtn_red_get(x_type, x_field, leftleft)) { \ x_type *tnode; rbtn_black_set(x_type, x_field, leftleft); rbtn_rotate_right(x_type, x_field, cnode, tnode); cnode = tnode; } } ... } ... ``` 首先要注意到 for loop 的 initial condition 是 **pathp - -** 也就是 pathp 改為**指向 new node 的 parent**. ```c if (pathp->cmp < 0) ``` 利用結構體 `x_prefix##path_entry_t` 中的成員 `cmp` 來做為判斷 **pathp 所指向的 node 的 children** 在插入 new node 所經過的 path 中為 left child 還是 right child, 接下來從經過的 path 回推到 root ,此時的 for loop 要做的事情確認插入的 node 是否違反 red black tree 的定義. ```c if (leftleft && rbtn_red_get(x_type, x_field, leftleft)) ``` 若 path 中有連續兩個 node 為 red 則需要 right rotate 而判斷的條件為上述的 code. ```c ... else { x_type *tnode; bool tred = rbtn_red_get(x_type, x_field, cnode); rbtn_rotate_left(x_type, x_field, cnode, tnode); rbtn_color_set(x_type, x_field, tnode, tred); rbtn_red_set(x_type, x_field, cnode); cnode = tnode; } ... ``` 而這段為對 **left leaning** 的情況,其中 left leaning 為 left child 為黑 ,right child 為 red 此時需要 left rotate. 這邊值得注意的是 , **rbtn_color_set**(xtype,x_field, tnode, tred)的使用, 先利用 bool tred 判斷 cnode 的顏色 , 其中判斷式為以下: ```c ((bool) (((uintptr_t) (x_node)->x_field.right_red) & 1)) ``` 而 bool tred 用在 rbtn_color_set() 使得 tnode 得到 cnode 的 color. ### Remove 一開始和 insert() 一樣,先 search ,這裡我只列出不同的地方,當找到要 remove 的 node 時,也要尋找它的**右子樹中最小的值**,也就是 **leftmost 的 node.** 利用 nodep = pathp 紀錄要 remove 的 node , 並利用 for loop 找到最靠左的 node,當 for loop 結束時 **pathp 指向 leftmost node的 left child** 且此 child 為 NULL.之後也利用 **pathp - -** 回到 leftmost node.而此 leftmost node 也是用來取代預刪除 node 的位置. ```c for (pathp = path; pathp->node; pathp++) { ... if (cmp == 0) { pathp->cmp = 1; nodep = pathp; for (pathp++; pathp->node; pathp++) { pathp->cmp = -1; pathp[1].node =rbtn_left_get(x_type, x_field, pathp->node); } break; } ... } ``` 再來的 if-else statement 用來判斷預要刪除的 node 是否為 leaf node. 下列的 code 為 ***remove node 非 leaf*** , 所以將 remove node 的左子樹和右子樹和 leftmost node 連接在一起,而其中 if(nodep == path) 用來判斷 nodep 是否為 rb tree 的 root ,因為刪除的 node 有可能在原先的 rb tree 為 root. ```c if (pathp->node != node) { ... rbtn_left_set(x_type, x_field, pathp->node,rbtn_left_get(x_type, x_field, node)); rbtn_right_set(x_type, x_field, pathp->node,rbtn_right_get(x_type, x_field, node)); ... nodep->node = pathp->node; pathp->node = node; if (nodep == path){ ... }else{ ... } } ``` 而接著為 ***remove node 為 leaf***.而在之前找尋 leftmost node 是屬於 remove node 的右子樹,所以也要額外確認 remove node 的左子樹,若存在,則和 parent node 做連接 , 其中 **pathp[-1]** 即是 parent node 的表示.再者刪除的 node 為 leaf 所以必為紅色,**做完後就能夠直接 return**. ```c else{ x_type *left = rbtn_left_get(x_type, x_field, node); if (left) { if(pathp==path){ ... } else{ if (pathp[-1].cmp < 0) { rbtn_left_set(x_type, x_field, pathp[-1].node, left); } else { btn_right_set(x_type, x_field, pathp[-1].node, left); } } } else{ ... } } ``` 所以接下來要探討的是,刪除的為黑色的 node 時 , path[0],path[1],...,path[-1] 要怎麼更新才能符合 left leaning red black tree 的規範. 目前 pathp 指向用來替代的 node , 若**此替代的 node 為黑色,則需要重新 balance** leaning left red black tree.這裡的 balance 為每段 在 llrb tree 的 path 上的黑色 node 數目相同. 在此之前,已經完成兩種情況的 remove (1)刪除的 node 為leaf (2)替代的 node 為 red , 不影響 balance 這裡說明只有替代的 node 為其 parent 的 right child 的情況,舉例來說以**左下**為範例, **7 為替代的 node** , 5 為 pathp 所指向的 node ![](https://i.imgur.com/MMuHu8t.jpg =45%x)![](https://i.imgur.com/1RptGGH.jpg =55%x) 替代的 node 為右子樹要探討的情況更多,**因為 left leaning 的設定所以不用去探討右上圖的情況**,因此簡單了 red black tree 的實作 :::success 因為要 balance , 所以目標是要讓所有 path 都有相同數目的黑色 node. ::: 第 1 種 : pathp 為 red , pathp 的左子點,利用 rbtn_left_get()得到並命名為 **left** 且**為黑色**, **leftleft** 為 left 的左子點且**為紅色**. ```c /* || pathp(r) / \ b b ==> / r */ ... rbtn_black_set(x_type, x_field, pathp->node); rbtn_red_set(x_type, x_field, left); rbtn_black_set(x_type, x_field, leftleft); rbtn_rotate_right(x_type, x_field, pathp->node,tnode); ... ``` 第 2 種 第 3 種 :::info ToDo: 比較不同寫法的紅黑樹的效能 ::: ### 研究 [jemalloc/rb.c](https://github.com/jemalloc/jemalloc/blob/dev/test/unit/rb.c) --- ## 測驗三 ### 將測驗 1 對節點的紅色和黑色標注的技巧嵌入到指標變數中 在 rbi.h 中,關於 color 的存放使用 type bool 的 object red 來紀錄 node 的顏色,程式如下 ```c struct rb_node{ struct rb_node *left, *right; bool red; } ``` 而測驗 1 中的 rb.h 利用記憶體對齊的特性,舉例來說,`__attribute**(aligned(sizeof(long)))` 讓記憶體地址以 4 為倍數成長,則後兩個 bits 用不到可以用來紀錄 node 的 color. ```c struct rb_node { struct rb_node *left,*right_red; } ``` 參照 rb.h , 將 rbi.c 的 function 改寫 , 舉例來說 : rbtn_right_get() , 因為前面的 62 個 bits 為真正的 pointer , 所以變為 ```c ((x_type*)(((intptr_t)((x_node)->x_field.right)) & ~3)) ``` 再者 , rbtn_black_set() 和 rbtn_red_set() 改寫成 ```c #define rbtn_red_set(x_type, x_field, x_node) \ do { \ (x_node)->x_field.right = (x_type*)((uintptr_t)((x_node)->x_field.right) | 1); \ } while (0) #define rbtn_black_set(x_type, x_field, x_node) \ do { \ (x_node)->x_field.right = \ (x_type*)((intptr_t)((x_node)->x_field.right) & ~3); \ } while (0) ``` ### 比對 [linux/rbtree.h](https://github.com/torvalds/linux/blob/master/include/linux/rbtree.h) 根據上述的例子,Linux 處理親代節點指標的方式是,將原本只用 1 個 bits 來紀錄 node 的 color 變成用 2 個 bits 來紀錄 node 的 color 和 parent 的 color ```c struct rb_node { unsigned long __rb_parent_color; struct rb_node *rb_right; struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); ``` ### rb_tree_cached 的機制