# 2023q1 Homework1 (quiz1) contributed by < [Welly0902](https://github.com/Welly0902) > ## 測驗一 **延伸問題** - [x] 解釋程式碼運作原理 - [ ] 針對 [Quick sort](https://en.wikipedia.org/wiki/Quicksort) 提出程式碼的改進方案並實作 - [ ] 引入 hybrid sort 策略並在 Linux 核心風格的 circular doubly-linked list 實作,需要量化性能表現並探討不同資料集 (data set) 的影響 - [ ] ==BONUS==: 嘗試對 Linux 核心提出排序程式碼改進的貢獻 --- Circular doubly-linked list 節點 structure 定義如下: ```c #include <stdint.h> #include "list.h" struct item { uint16_t i; struct list_head list; }; ``` 再來是節點內含值比較的函式: ```c static inline int cmpint(const void *p1, const void *p2) { const uint16_t *i1 = (const uint16_t *) p1; const uint16_t *i2 = (const uint16_t *) p2; return *i1 - *i2; } ``` 可看到 `cmpint` 並不是直接 `return *p1 - *p2;`,故接下來會觀察一下 `uint16_t` 這個資料型態是否有它必要的意義。 再來看一下 sorting 的程式: ```c static void list_sort(struct list_head *head) { //1 if (list_empty(head) || list_is_singular(head)) return; //2 struct list_head list_less, list_greater; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); //3 AAA BBB struct item *pivot = list_first_entry(head, item, list); list_del(&pivot->list); //4 struct item *itm = NULL, *is = NULL; // CCC list_for_each_entry_safe (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) // DDD list_move_tail (&itm->list, &list_less); else // EEE list_move_tail (&itm->list, &list_greater); } //5 list_sort(&list_less); list_sort(&list_greater); //6 list_add(&pivot->list, head); list_splice(&list_less, head); // FFF list_splice_tail(&list_greater, head); } ``` 一段一段拆解來看,首先: ```c if (list_empty(head) || list_is_singular(head)) return; ``` 參照 [list.h](https://github.com/sysprog21/lab0-c/blob/master/list.h) 說明,我們可以看到`list_empty`的程式碼: ```c static inline int list_empty(const struct list_head *head) { return (head->next == head); } ``` return `1` 代表 list 是空的,也就是說的 `head->next == head` 時 list 是空的,這裡我不太確定 circular doubly-linked list 當一開始空的時候長怎樣,所以我們回去看 `list.h` 中的 `INIT_LIST_HEAD`: ```c static inline void INIT_LIST_HEAD(struct list_head *head) { head->next = head; head->prev = head; } ``` 畫成圖為: ```graphviz digraph g { rankdir=LR; node [shape=record, colorscheme=reds9]; head [label="{<ref1> prev | <data> head | <ref2> next}"]; head:ref2 -> head:data:n; head:ref1 -> head:data:s; } ``` 所以從上圖我們可看出 `head->next == head` 確實為只有 head 的初始狀態,為空。 接下來看一下 `list_is_singular` function,同樣查看 `list.h` 了解它的目的是為了去檢驗是否剛好只有一個節點在此。看一下下方程式碼: ```c static inline int list_is_singular(const struct list_head *head) { return (!list_empty(head) && head->prev == head->next); } ``` 可看條件為非空且 `head->prev` 與 `head->next` 指到同一個 node。 ```graphviz digraph g { rankdir=LR; node [shape=record, colorscheme=reds9]; head [label="{<ref1> prev | <data> head | <ref2> next}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; head:ref2 -> a:data:n; head:ref1 -> a:data:s; } ``` ```graphviz digraph graphname{ compound=true rankdir=LR node[shape=box] D[label=5] F[label=7] B[label=1] C[label=3] E[label=2] pivot[shape=plaintext] A[label=4] { rank=same pivot->A } subgraph { subgraph cluster_greater { labeljust=l label="list_greater" D->F; } subgraph cluster_less { labeljust=l label="list_less" B->C->E; } E->D[style=invis] } } ``` 所以回到 sorting 原程式,`//1` 先檢查如果 input list 為空或只有一個節點,那直接回傳即為排列好的 list。再來 `//2` 的部分 initialize 了兩個 pointer `list_less` 和 `list_greater`: ```graphviz digraph g { rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}", color=6]; h2 [label="{<ref1> | <data> list_greater | <ref2>}",color=6]; h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; subgraph { a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; // c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref2:c -> a:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h1:ref2 -> h1:data:n [color=red]; h1:ref1 -> h1:data:s [color=red]; h2:ref2 -> h2:data:n [color=red]; h2:ref1 -> h2:data:s [color=red]; } } ``` 再來 `//3` 的部分首先先看 `list_first_entry` macro 的程式碼: ```c #define list_first_entry(head, type, member) \ list_entry((head)->next, type, member) ``` 可以看到運用了另一個 macro `list_entry`: ```c #define list_entry(node, type, member) container_of(node, type, member) ``` 裡面又連結到另一個 `list.h` 中的 macro `container_of`,並根據 [ifdef, ifndef](https://docs.revenera.com/installshield26helplib/LangRef/Langrefifdef_and_ifndef.htm#:~:text=Use%20the%20%23ifdef%20statement%20when,expression%20has%20not%20been%20defined.) 定義加上 indentation 以方便閱讀: ```c #ifndef container_of #ifdef __LIST_HAVE_TYPEOF #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) #else #define container_of(ptr, type, member) \ ((type *) ((char *) (ptr) -offsetof(type, member))) #endif #endif ``` 對於 `__LIST_HAVE_TYPEOF` 檢查是否允許 GNUC extension 有關條件的說明可參考[之前同學](https://hackmd.io/@RinHizakura/HkEuhNwGO)的說明。這裡直接看 * 延伸閱讀: [為何需要 char * casting](https://stackoverflow.com/questions/20421910/the-char-casting-in-container-of-macro-in-linux-kernel)、[macro container_of explained](https://psomas.wordpress.com/2009/07/01/weird-kernel-macros-container_of/)、[kobject 基礎介紹](https://medium.com/powerof2/the-kernel-kobject-device-model-explained-89d02350fa03) 而這裡透過詢問 chatgpt,可快速了解 `container_of` macro 所代表的意義 > Q: container_of in linux kernel > ChatGPT: container_of is a powerful and commonly used macro in the Linux kernel that allows a developer to obtain a pointer to a structure containing a given field. The container_of macro takes three arguments: a pointer to a field in a structure, the name of the structure containing the field, and the name of the field itself. The macro then computes the offset of the field within the structure and returns a pointer to the containing structure. ::: 所以我們可知 `list_entry` 可讓我們得到一個 member pointer 用來取用目前 pointer 指向節點內的 member,以取用節點內 member進行操作。而`list_first_entry` 則可得到指向目前 linked list 中第一個節點的 mpointer 來對其進行操作。 回到 `//3` 的程式碼,從說明可以快速推出 AAA 與 BBB 為 `item` 和 `list`。 ```c * @type: type of the entry containing the list node * @member: name of the list_head member variable in struct @type ``` 再來看程式碼: ```c //3 AAA BBB struct item *pivot = list_first_entry(head, item, list); list_del(&pivot->list); ``` 首先第一行: ```graphviz digraph g { label="list_entry((head)->next, item, list)" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; subgraph { a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; // c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref2:c -> a:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n [color=red] h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; } } ``` 再來第二行的 `list_del` 程式碼如下: ```c static inline void list_del(struct list_head *node) { struct list_head *next = node->next; struct list_head *prev = node->prev; next->prev = prev; prev->next = next; #ifdef LIST_POISONING node->prev = (struct list_head *) (0x00100100); node->next = (struct list_head *) (0x00200200); #endif } ``` > LIST_POISONING can be enabled during build-time to provoke an invalid memory access when the memory behind the next/prev pointer is used after a list_del. This only works on systems which prohibit access to the predefined memory addresses. 這裡做 deletion 是為了先將選中的 pivot (此程式是選 list 的第一個元素當作 pivot) 當作分隔 list 中 node 的 i 值大小的依據。Deletion 過程如下圖: ```graphviz digraph g { label="struct list_head *next = node->next;" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] h4 [shape=plaintext, label="next", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; subgraph { a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; // c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref2:c -> a:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n h4:n -> b:n [color=red] h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; } } ``` ```graphviz digraph g { label="struct list_head *prev = node->prev;" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] h4 [shape=plaintext, label="next", fontname="Arial"] h5 [shape=plaintext, label="prev", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; subgraph { a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; // c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref2:c -> a:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n h4:n -> b:n h5:n -> c:n [color=red] h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; } } ``` ```graphviz digraph g { label="next->prev = prev;" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] h4 [shape=plaintext, label="next", fontname="Arial"] h5 [shape=plaintext, label="prev", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; subgraph { a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; // c:ref2:c -> a:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref2:c -> a:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n h4:n -> b:n h5:n -> c:n h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; } } ``` ```graphviz digraph g { label="prev->next = next;" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] h4 [shape=plaintext, label="next", fontname="Arial"] h5 [shape=plaintext, label="prev", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; subgraph { a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; c:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red]; h0:ref2:c -> a:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n h4:n -> b:n h5:n -> c:n h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; } } ``` 由上圖可以看出 n2 及 n3 經過 pivot 指向的 node n1 deletion 後已經成為 一個 circular doubly-linked list,不過從此處程式碼看不出: 1. n1 節點剩餘 link 的處理 2. list head 也沒有重新指到新的頭尾 這兩個問題暫時還沒找到解答。 再來先繼續往下看主程式: ```c //4 struct item *itm = NULL, *is = NULL; // CCC list_for_each_entry_safe (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) // DDD list_move_tail (&itm->list, &list_less); else // EEE list_move_tail (&itm->list, &list_greater); } ``` 首先先建立等等 for each 會用到的 member pointer: ```graphviz digraph g { label="struct item *itm = NULL, *is = NULL;" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] // h4 [shape=plaintext, label="next", fontname="Arial"] // h5 [shape=plaintext, label="prev", fontname="Arial"] h6 [shape=circle, label=itm, color=6] h7 [shape=circle, label=is, color=6] h8 [shape=plaintext, label="null", fontname="Arial"] h9 [shape=plaintext, label="null", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> n1 | <ref2>}"]; b [label="{<ref1> | <data> n2 | <ref2>}"]; c [label="{<ref1> | <data> n3 | <ref2>}"]; subgraph { // a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; c:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; h0:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n; // h4:n -> b:n; // h5:n -> c:n; h6 -> h8; h7 -> h9; h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; } } ``` 接下來先看一下 `list_move_tail` 的程式: ```c static inline void list_move_tail(struct list_head *node, struct list_head *head) { list_del(node); list_add_tail(node, head); } ``` 以及 `list_add_tail` 的程式: ```c static inline void list_add_tail(struct list_head *node, struct list_head *head) { struct list_head *prev = head->prev; prev->next = node; node->next = head; node->prev = prev; head->prev = node; } ``` 那我們假設目前的 list 如下圖: ```graphviz digraph g { // label="struct item *itm = NULL, *is = NULL;" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] // h4 [shape=plaintext, label="next", fontname="Arial"] // h5 [shape=plaintext, label="prev", fontname="Arial"] h6 [shape=circle, label=itm] h7 [shape=circle, label=is] h8 [shape=plaintext, label="null", fontname="Arial"] h9 [shape=plaintext, label="null", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> 6 | <ref2>}"]; b [label="{<ref1> | <data> 3 | <ref2>}"]; c [label="{<ref1> | <data> 8 | <ref2>}"]; subgraph { // a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; c:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; h0:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n; // h4:n -> b:n; // h5:n -> c:n; h6 -> h8; h7 -> h9; h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; } } ``` 則當我們利用 `cmpint` 與 pivot相減後,以上圖例子來看,首先`3-6<0`,故會執行`list_move_tail (&itm->list, &list_less);`: ```graphviz digraph g { label="list_move_tail (&itm->list, &list_less);" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] // h4 [shape=plaintext, label="next", fontname="Arial"] // h5 [shape=plaintext, label="prev", fontname="Arial"] h6 [shape=circle, label=itm] h7 [shape=circle, label=is] h8 [shape=plaintext, label="null", fontname="Arial"] h9 [shape=plaintext, label="null", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> 6 | <ref2>}"]; b [label="{<ref1> | <data> 3 | <ref2>}"]; c [label="{<ref1> | <data> 8 | <ref2>}"]; subgraph { // a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; c:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; h0:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n; // h4:n -> b:n; // h5:n -> c:n; h6 -> h8; h7 -> h9; h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; } } ``` 先執行 `list_del(&itm->list)`(當下節點為 3): ```graphviz digraph g { label="list_del(&itm->list);" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] // h4 [shape=plaintext, label="next", fontname="Arial"] // h5 [shape=plaintext, label="prev", fontname="Arial"] h6 [shape=circle, label=itm] h7 [shape=circle, label=is] h8 [shape=plaintext, label="null", fontname="Arial"] h9 [shape=plaintext, label="null", fontname="Arial"] h10 [shape=plaintext, label="node", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> 6 | <ref2>}"]; b [label="{<ref1> | <data> 3 | <ref2>}", color=6]; c [label="{<ref1> | <data> 8 | <ref2>}"]; subgraph { // a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; // b:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; // b:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; c:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; c:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; h0:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n; // h4:n -> b:n; // h5:n -> c:n; h6 -> h8; h7 -> h9; h1:ref2 -> h1:data:n; h1:ref1 -> h1:data:s; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; h10 -> b:data [color=red]; } } ``` 接著 `list_add_tail(&itm->list, &list_less)`: ```graphviz digraph g { label="list_add_tail(&itm->list, &list_less);" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; b [label="{<ref1> | <data> 3 | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] // h4 [shape=plaintext, label="next", fontname="Arial"] // h5 [shape=plaintext, label="prev", fontname="Arial"] h6 [shape=circle, label=itm] h7 [shape=circle, label=is] h8 [shape=plaintext, label="null", fontname="Arial"] h9 [shape=plaintext, label="null", fontname="Arial"] h10 [shape=plaintext, label="node", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> 6 | <ref2>}"]; c [label="{<ref1> | <data> 8 | <ref2>}"]; subgraph { // a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; h1:ref2 -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red, label="//1"]; b:ref2:c -> h1:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red, label="//2"]; h1:ref1:c -> b:data:w[weight = 100, style = invis]; b:ref1:c -> h1:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red, label="//3"]; h1:ref1 -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red, label="//4"]; c:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; // c:ref1:c -> b:data:w[weight = 100, style = invis]; // b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; c:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; h0:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n; // h4:n -> b:n; // h5:n -> c:n; h6 -> h8; h7 -> h9; h2:ref2 -> h2:data:n; h2:ref1 -> h2:data:s; h10 -> b:data; } } ``` 當執行完 主程式的 `//4`,可得下圖: ```graphviz digraph g { // label="list_add_tail(&itm->list, &list_less);" rankdir=LR; node [shape=record, colorscheme=reds9]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; b [label="{<ref1> | <data> 3 | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; h3 [shape=plaintext, label="pivot", fontname="Arial"] // h4 [shape=plaintext, label="next", fontname="Arial"] // h5 [shape=plaintext, label="prev", fontname="Arial"] // h10 [shape=plaintext, label="node", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> 6 | <ref2>}"]; c [label="{<ref1> | <data> 8 | <ref2>}"]; subgraph { h1 -> b [dir=both] h2 -> c [dir=both] // a:ref2:c -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; // h1:ref2 -> b:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red, label="//1"]; // b:ref2:c -> h1:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red, label="//2"]; // h1:ref1:c -> b:data:w[weight = 100, style = invis]; // b:ref1:c -> h1:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red, label="//3"]; // h1:ref1 -> b:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, color=red, label="//4"]; // c:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; // c:ref1:c -> b:data:w[weight = 100, style = invis]; // b:ref1:c -> a:data:w[weight = 100, style = invis]; // a:ref1:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; // c:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false]; // h0:ref2:c -> c:data:n [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; // h0:ref1:c -> c:data:s [arrowhead=normal, arrowtail=none, dir=both, tailclip=false, style=dashed]; h3:n -> a:n; // h4:n -> b:n; // h2:ref2 -> h2:data:n; // h2:ref1 -> h2:data:s; // h10 -> b:data; } } ``` 接著進行到 `//5` 時,會將 `list_less` 和 `list_greater` 中的值遞迴排序好。 排序好後會剩下 `list_less` 和 `list_greater` 的 list 和 pivot node 並執行 `//6`。 在執行前先看一下 `list_splice` 的程式碼: ```c static inline void list_splice(struct list_head *list, struct list_head *head) { struct list_head *head_first = head->next; struct list_head *list_first = list->next; struct list_head *list_last = list->prev; if (list_empty(list)) return; head->next = list_first; list_first->prev = head; list_last->next = head_first; head_first->prev = list_last; } ``` 以及 `list_splice_tail` 的程式碼: ```c static inline void list_splice_tail(struct list_head *list, struct list_head *head) { struct list_head *head_last = head->prev; struct list_head *list_first = list->next; struct list_head *list_last = list->prev; if (list_empty(list)) return; head->prev = list_last; list_last->next = head; list_first->prev = head_last; head_last->next = list_first; } ``` 在 `//6` 中先執行 `list_add(&pivot->list, head)` 將 pivot 加到 head pointer 最前面: ```graphviz digraph g { label="list_add(&pivot->list, head);" rankdir=LR; node [shape=record, colorscheme=reds9]; h3 [shape=plaintext, label="pivot", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> 6 | <ref2>}", color=6]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; b [label="{<ref1> | <data> 3 | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; c [label="{<ref1> | <data> 8 | <ref2>}"]; subgraph { h0:c -> a [dir=both] h1 -> b [dir=both] h2 -> c [dir=both] h3 -> a:data; } } ``` ```graphviz digraph g { label="list_splice(&list_less, head);" rankdir=LR; node [shape=record, colorscheme=reds9]; // h3 [shape=plaintext, label="pivot", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> 6 | <ref2>}"]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; b [label="{<ref1> | <data> 3 | <ref2>}", color=6]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; c [label="{<ref1> | <data> 8 | <ref2>}"]; subgraph { // h0:c -> a [dir=both] h0:c -> b [dir=both, color=red] h0:ref1:c -> a:ref2:n [dir=both] b -> a [dir=both, color=red] h1 -> b [dir=both] h2 -> c [dir=both] // h3 -> a:data; } } ``` `list_splice_tail` 時從 head->prev 也就是 head 這個 linked list的最後一個節點(6)進行操作: ```graphviz digraph g { label="list_splice_tail(&list_greater, head);" rankdir=LR; node [shape=record, colorscheme=reds9]; // h3 [shape=plaintext, label="pivot", fontname="Arial"] h0 [label="{<ref1> | <data> head | <ref2>}"]; a [label="{<ref1> | <data> 6 | <ref2>}"]; h1 [label="{<ref1> | <data> list_less | <ref2>}"]; b [label="{<ref1> | <data> 3 | <ref2>}"]; h2 [label="{<ref1> | <data> list_greater | <ref2>}"]; c [label="{<ref1> | <data> 8 | <ref2>}"]; subgraph { // h0:c -> a [dir=both] h0:c -> b [dir=both]; b -> a [dir=both]; h1 -> b [dir=both]; h2 -> c [dir=both]; // h3 -> a:data; a -> c [dir=both, color=red]; h0:ref1:c -> c:ref2:n [dir=both, color=red] } } ``` --- ## 測驗二 **延伸問題** - [ ] 解釋程式碼運作原理 - [ ] 針對 [Quick sort](https://en.wikipedia.org/wiki/Quicksort) 提出程式碼的改進方案並實作 - [ ] 引入 hybrid sort 策略並在 Linux 核心風格的 circular doubly-linked list 實作,需要量化性能表現並探討不同資料集 (data set) 的影響 - [ ] ==BONUS==: 嘗試對 Linux 核心提出排序程式碼改進的貢獻 ## 測驗三 **延伸問題** - [ ] 解釋上述程式碼運作原理,指出其實作缺陷並改進 - [ ] 在上述 XOR linked list 的基礎,實作測驗 `1` 和 `2` 提及的快速排序演算法,注意要引入你改進效能的版本 - [ ] 修改 `xorlist_destroy`,使其不急著釋放記憶體,而搭配 [atexit(3)](https://man7.org/linux/man-pages/man3/atexit.3.html) 並在程式即將結束執行時,才批次處理資源釋放