--- tags: linux kernel --- # 2023q1 Homework1 (quiz1) contributed by < `hankTaro` > ## 測驗一 * 實作快速排序法,數值由小到大排列,並確保可達到 stable sorting 的目標。 ```c= static void list_sort(struct list_head *head) { if (list_empty(head) || list_is_singular(head)) return; struct list_head list_less, list_greater; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); struct item *pivot = list_first_entry(head, AAA, BBB); list_del(&pivot->list); struct item *itm = NULL, *is = NULL; CCC (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) DDD (&itm->list, &list_less); else EEE (&itm->list, &list_greater); } list_sort(&list_less); list_sort(&list_greater); list_add(&pivot->list, head); list_splice(&list_less, head); FFF(&list_greater, head); } ``` * 此為遞迴模式的 quick sort * 利用`Divide and Conquer` 1. 找出中間節點 `pivot` 2. 以 `pivot` 為比較值,分出值大於/小於 `pivot` 的兩群,重複進行 `list_sort` ,直到分不出新的大小群為止 ```c //將第一個節點設為 pivot,並將此點從串列中移出 struct item *pivot = list_first_entry(head, AAA, BBB); list_del(&pivot->list); ``` 其中 `list_first_entry` 的參數是 `#define list_first_entry(head, type, member)` ,所以 `AAAA = struct item` ,`BBBB = list` ```c //節點結構 struct item { uint16_t i; struct list_head list; }; ``` ```c= // 群訪各點判斷其與 `pivot` 的大小關係,並分群 struct item *itm = NULL, *is = NULL; CCC (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) DDD (&itm->list, &list_less); else EEE (&itm->list, &list_greater); } // 將兩群分別進行遞迴 list_sort(&list_less); list_sort(&list_greater); // 將串列依據合併 list_add(&pivot->list, head); list_splice(&list_less, head); FFF(&list_greater, head); ``` * 其中 `CCC = list_each_entry_safe` ,尋訪各點並且支援在尋訪期間移動當前節點移至其他串列中 * `if (cmpint(&itm->i, &pivot->i) < 0)` 由於是取第一個節點,所以與他相將等的值,必定在其後方,因此比較時使用 `<` 而非 `<=` 使與他相等的的節點存入 `greater` 中,以保持 ==stable== * 利用 `list_move_tail` 將當前節點分別移到 `list_less` 和 `list_greater` 的尾端,由於 `list_each_entry_safe` 是正向尋訪,所以需要使用 `list_move_tail` 而非 `list_move` 以保持結果 ==stable== ,所以 `DDD = EEE = list_move_tail` * 最後將大小串列合併,將小於 `pivot` 的放於 `pivot` 與 `head` 之間,將大於 `pivot` 的串列放於 `pivot` 之後,故 `FFF = list_splice_tail` ### (代辦)針對 Quick sort 提出上述程式碼的改進方案並實作 引入 hybrid sort 策略並在 Linux 核心風格的 circular doubly-linked list 實作,需要量化性能表現並探討不同資料集 (data set) 的影響 ## 測驗二 * 將測驗一的程式碼改為非遞迴 ```c= #define MAX_DEPTH 512 static void list_sort_nr(struct list_head *head) { if (list_empty(head) || list_is_singular(head)) return; struct list_head stack[MAX_DEPTH]; for (int i = 0; i < MAX_DEPTH; i++) INIT_LIST_HEAD(&stack[i]); int top = -1; list_splice_init(head, &stack[++top]); struct list_head partition; INIT_LIST_HEAD(&partition); while (top >= 0) { INIT_LIST_HEAD(&partition); list_splice_init(&stack[top--], &partition); if (!list_empty(&partition) && !list_is_singular(&partition)) { struct list_head list_less, list_greater; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); struct item *pivot = list_first_entry(&partition, struct item, list); list_del(&pivot->list); INIT_LIST_HEAD(&pivot->list); struct item *itm = NULL, *is = NULL; GGGG (itm, is, &partition, list) { list_del(&itm->list); if (cmpint(&itm->i, &pivot->i) < 0) list_move(&itm->list, &list_less); else list_move(&itm->list, &list_greater); } HHHH (&pivot->list, &list_less); if (!list_empty(&list_greater)) list_splice_tail(&list_greater, IIII); if (!list_empty(&list_less)) list_splice_tail(&list_less, JJJJ); } else { top++; list_splice_tail(&partition, &stack[top]); while (top >= 0 && list_is_singular(&stack[top])) { struct item *tmp = list_first_entry(&stack[top], struct item, list); list_del(&tmp->list); INIT_LIST_HEAD(KKKK); list_add_tail(&tmp->list, head); } } } } ``` ### 分段說明 * 初始化,將愈進行 quick sort 的串列放入 stack[0] 中 ```c= // 將 stack[0] 指向 head 後的串列,並將 head 指向自身 struct list_head stack[MAX_DEPTH]; for (int i = 0; i < MAX_DEPTH; i++) INIT_LIST_HEAD(&stack[i]); int top = -1; list_splice_init(head, &stack[++top]); ``` 進入 `while` 後,將 `stack[top]` 中的值 `pop()` 進入 `partition` ,並且判別其值是否可再次分割,若可以,隨後所做的事與遞迴版本無異,都是先找出 `pivot` 並分出 less 和 greater 兩群,唯一不同的是這裡使用的是 `list_move` 而非 `list_move_tail` 會使其 ==non-stable== ,其餘與與測驗一相同,下方的程式碼 `GGGG = list_each_entry_safe` ```c= while (top >= 0) { // 將從 stack 中 pop() 出的值放入 partition INIT_LIST_HEAD(&partition); list_splice_init(&stack[top--], &partition); if (!list_empty(&partition) && !list_is_singular(&partition)) { /* . . . */ struct item *itm = NULL, *is = NULL; GGGG (itm, is, &partition, list) { // 不必 remove 因為在 `list_move` 時,自然就會被移出 // list_del(&itm->list); if (cmpint(&itm->i, &pivot->i) < 0) list_move(&itm->list, &list_less); else list_move(&itm->list, &list_greater); } /* . . . */ } } ``` 而接下來的分治部分,先將 pivot 併入 list_less 這群中,再利用 stack 的 pop 和 push 實現 其中 `HHHH = list_move_tail` ,而 `IIII = JJJJ = &stack[++top]` 將兩群分別 `push()` 進入 stack 這裡 `push()` 的順序極為重要,會影響到後續程式碼的寫法,以下是以先 `push(greater)` 再 `push(less)` ,所以 `less` 方的會最先被分割到無法分割,也就是最終越接近頂端的值最小,隨後合併時要依序從上到下的放入 `head` 中,就會是由小到大的 (詳見圖解) ```c= // 將 pivot 併入 list_less 這群中 HHHH (&pivot->list, &list_less); if (!list_empty(&list_greater)) list_splice_tail(&list_greater, IIII); if (!list_empty(&list_less)) list_splice_tail(&list_less, JJJJ); ``` 當 `pop()` 出的值不可再次分割,由於 `less` 總是後 `push()` 所以會最早分割完,使不可分割值由大到小向上堆疊,所以依序將 stack 中 pop() 出的值 `list_add_tail` 至 `head`,直到遇上可分割的值為止 * `KKKK = &stack[top--]` 作為 `pop()` * 另外 while 中做的事,與 `list_splice_tail_init(&stack[top--], head);` 等價,可直接用其替代 ```c= else { top++; list_splice_tail(&partition, &stack[top]); while (top >= 0 && list_is_singular(&stack[top])) { struct item *tmp = list_first_entry(&stack[top], struct item, list); list_del(&tmp->list); INIT_LIST_HEAD(KKKK); list_add_tail(&tmp->list, head); } } ``` #### 圖例 * 圖一 ```graphviz digraph graphname { rankdir="LR" node [shape=record] // label="divide" subgraph cluster_0 { label="stack[top]" peripheries=0 stack[label="<f0>|<f1>|<f2>|<f3>list_less(1)|<f4>list_greater(1)"] } list_1[label="{<f0>3|<f1>2|<f2>1|<f3>6|<f4>4}"] list_2[label="{<f0>7|<f1>10|<f2>13|<f3>8|<f4>12}"] stack:f3 -> list_1 stack:f4 -> list_2 ptr [label=top ,shape=plaintext] ptr -> stack:f3 } ``` * 圖二 `partition = pop(top)` 也就是 `list_splice_init(&stack[top--], &partition);` ```graphviz digraph graphname { rankdir="LR" node [shape=record] // label="divide" subgraph cluster_0 { label="stack[top]" peripheries=0 stack[label="<f0>|<f1>|<f2>|<f3>|<f4>list_greater(1)"] } list_1[label="{<f0>3|<f1>2|<f2>1|<f3>6|<f4>4}"] list_2[label="{<f0>7|<f1>10|<f2>13|<f3>8|<f4>12}"] // stack:f3 -> list_1 stack:f4 -> list_2 ptr [label=top ,shape=plaintext] ptr -> stack:f4 partition[label=partition ,shape=plaintext] partition -> list_1 } ``` * 圖三 將其分群後使用 `list_splice_tail(&list_greater, &stack[++top]);` `list_splice_tail(&list_less, &stack[++top]);` ```graphviz digraph graphname { rankdir="LR" node [shape=record] // label="divide" subgraph cluster_0 { label="stack[top]" peripheries=0 stack[label="<f0>|<f1>|<f2>list_less(2)|<f3>list_greater(2)|<f4>list_greater(1)"] } list_1[label="{<f0>3|<f1>2|<f2>1}"] list_2[label="{<f0>7|<f1>10|<f2>13|<f3>8|<f4>12}"] list_3[label="{<f3>6|<f4>4}"] stack:f3 -> list_3:f0 stack:f4 -> list_2:f0 stack:f2 -> list_1:f0 ptr [label=top ,shape=plaintext] ptr -> stack:f7 } ``` ### (代辦)效能分析 ### (代辦)改進辦法與成果 #### 必免依賴 `MAX_DEPTH` 此程式碼的 wrost case 就是傳入遞減數列,會使得每一次 `push()` 進去的串列大小為 $n-1$ ,最終會占用到 `stack[n]` 才有辦法合併 ```graphviz digraph graphname { rankdir="LR" node [shape=record] // label="divide" subgraph cluster_0 { label="stack[top]" peripheries=0 stack[label="<f0>|<f1>[4,1]|<f2>[7]|<f3>[8]|<f4>[9]"] } // list_1[label="9",width=0.3, height=0.3] // list_2[label="8",width=0.3, height=0.3] // list_3[label="7",width=0.3, height=0.3] // list_4[label="{<f0>4|<f1>1}",width=0.3, height=0.3] // stack:f1 -> list_4 // stack:f2 -> list_3 // stack:f3 -> list_2 // stack:f4 -> list_1 ptr [label=top ,shape=plaintext] ptr -> stack:f1 } ``` ```graphviz digraph graphname { rankdir="LR" node [shape=record] // label="divide" subgraph cluster_0 { label="stack[top]" peripheries=0 stack[label="<f0>[1]|<f1>[4]|<f2>[7]|<f3>[8]|<f4>[9]"] } // list_1[label="9",width=0.3, height=0.3] // list_2[label="8",width=0.3, height=0.3] // list_3[label="7",width=0.3, height=0.3] // list_4[label="{<f0>4|<f1>1}",width=0.3, height=0.3] // stack:f1 -> list_4 // stack:f2 -> list_3 // stack:f3 -> list_2 // stack:f4 -> list_1 ptr [label=top ,shape=plaintext] ptr -> stack:f0 } ``` >>想法一: >>使用動態記憶體分配,將 `stack` 大小分配成 `list_count_nodes(head)` 的大小 > >>想法二: >>利用 hybrid sort 的概念,在不同深度使用不同排序法,進而減少運算時間並且降低 `stack` 最大所需空間 ```c= #define MAX_DEPTH 512 static void list_sort_nr(struct list_head *head) { if (list_empty(head) || list_is_singular(head)) return; struct list_head stack[MAX_DEPTH]; for (int i = 0; i < MAX_DEPTH; i++) INIT_LIST_HEAD(&stack[i]); // int top = -1; // list_splice_init(head, &stack[++top]); int top = 0;// 作為stack_ptr list_splice_init(head, &stack[0]); //減少沒必要的運算量 // struct list_head partition; // INIT_LIST_HEAD(&partition); INIT_LIST_HEAD(partition); // 可用一行指令表示 while (top >= 0) { INIT_LIST_HEAD(&partition); // 將資料 pop() 進 partition 中 list_splice_init(&stack[top--], &partition); // 與遞迴版本中一致,找出 pivot 並且分群出 less 和 greater if (!list_empty(&partition) && !list_is_singular(&partition)) { struct list_head list_less, list_greater; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); struct item *pivot = list_first_entry(&partition, struct item, list); list_del(&pivot->list); INIT_LIST_HEAD(&pivot->list); //尋訪各點進行分群 struct item *itm = NULL, *is = NULL; list_each_entry_safe(itm, is, &partition, list) { // 不必 remove 因為在 `list_move` 時,自然就會被移出 // list_del(&itm->list); if (cmpint(&itm->i, &pivot->i) < 0) // list_move(&itm->list, &list_less); //使其 stable list_move_tail(&itm->list, &list_less); else // list_move(&itm->list, &list_greater); //使其 stable list_move_tail(&itm->list, &list_greater); } list_move_tail (&pivot->list, &list_less); if (!list_empty(&list_greater)) list_splice_tail(&list_greater, &stack[++top]); if (!list_empty(&list_less)) list_splice_tail(&list_less, &stack[++top]); } else { top++; list_splice_tail(&partition, &stack[top]); while (top >= 0 && list_is_singular(&stack[top])) { // struct item *tmp = // list_first_entry(&stack[top], struct item, list); // list_del(&tmp->list); // INIT_LIST_HEAD(KKKK); // list_add_tail(&tmp->list, head); list_splice_tail_init(&stack[top--], head); } } } } ``` ## 測驗三 LLL = (uintptr_t) a ^ (uintptr_t) b MMM = &list->tail PPP = real_next->cmp