# 2023q1 Homework1 (quiz1) contributed by < [Huaxin](https://github.com/p96114175) > > [題目](https://hackmd.io/@sysprog/linux2023-quiz1) ## 開發環境 ``` gcc --version gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. lscpu 架構: x86_64 CPU 作業模式: 32-bit, 64-bit Byte Order: Little Endian Address sizes: 39 bits physical, 48 bits virtual CPU(s): 24 On-line CPU(s) list: 0-23 每核心執行緒數: 1 每通訊端核心數: 16 Socket(s): 1 NUMA 節點: 1 供應商識別號: GenuineIntel CPU 家族: 6 型號: 151 Model name: 12th Gen Intel(R) Core(TM) i9-12900F 製程: 2 CPU MHz: 2400.000 CPU max MHz: 5100.0000 CPU min MHz: 800.0000 BogoMIPS: 4838.40 虛擬: VT-x L1d 快取: 384 KiB L1i 快取: 256 KiB L2 快取: 10 MiB ``` ## 回答測驗一 :::danger 注意作業書寫規範:中英文間用一個半形空白字元區隔。 程式碼縮排亦有相關規範,請依循! :notes: jserv ::: :::success 好的 ::: 首先從[Linux 核心 linked list 實作](https://hackmd.io/@sysprog/c-linked-list)你可以發現 > #define list_first_entry(head, type, member) \ list_entry((head)->next, type, member) 可以知道 `AAA` 表示 `type` , `BBB` 表示 `member` ,再從 ``` struct item *pivot = list_first_entry(head, AAA, BBB); ``` 可以得知 `AAA` 為 `item` 而 `BBB` 為 `list` ,另外針對下列程式碼的 `CCC` 和 `DDD` , `EEE` ``` CCC (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) DDD (&itm->list, &list_less); else EEE (&itm->list, &list_greater); } ``` 從程式碼內容我們可以知道 `CCC` 在迭代 (iteration)所操作的節點,其中 `itm` 來自 `list_entry((head)->next, __typeof__(*entry), member)`,`is` 來自 `list_entry(entry->member.next, __typeof__(*entry), member)` ,可以得知 `CCC` 為 `list_for_each_entry_safe` 。 對 `item` 和 `pivot` 使用 `cmpint` 比較值,若 `item` 小於 `pivot` ,便可以通過 `if` 判斷式,執行 `DDD (&itm->list, &list_less)` ,將 `item` 節點移動至 `list_less` 節點的尾端,若 `item` 大於 `pivot` ,將 `item` 節點移動至 `list_greater` 節點的尾端,可以得知 `DDD` 和 `EEE` 為 `list_move_tail` **原理的部分** ```c struct item *pivot = list_first_entry(head, AAA, BBB); ``` 1. 先取出 `head` 的第一個entry且以一個指標 `pivot` 指著 ```graphviz digraph { rankdir=LR node [shape="block"] node5 [label="5"] node4 [label="4"] node3 [label="3"] node2 [label="2"] null0 [shape=none, label="NULL"] head [shape=none, label="head"] pivot [shape=none, label="pivot"] pivot->node5 head->node5->node4->node3->node2->null0 {rank=same;node5 pivot} } ``` ```c list_del(&pivot->list); ``` ```graphviz digraph { rankdir=LR node [shape="block"] node5 [label="5"] node4 [label="4"] node3 [label="3"] node2 [label="2"] null0 [shape=none, label="NULL"] head [shape=none, label="head"] pivot [shape=none, label="pivot"] pivot->node5 head->node4->node3->node2->null0 } ``` ```c struct item *itm = NULL, *is = NULL; list_for_each_entry_safe (itm, is, head, list) { if (cmpint(&itm->i, &pivot->i) < 0) list_move_tail(&itm->list, &list_less); else list_move_tail(&itm->list, &list_greater); } ``` ```graphviz digraph { rankdir=LR node [shape="block"] node5 [label="5"] node4 [label="4"] node3 [label="3"] node2 [label="2"] null0 [shape=none, label="NULL"] head [shape=none, label="head"] pivot [shape=none, label="pivot"] list_less [shape=none,label="list_less"] list_geater [shape=none,label="list_geater"] empty1[label=""][shape=plaintext] empty2[label=""][shape=plaintext] head->empty1 list_geater->empty2 pivot->node5 list_less->node4->node3->node2->null0 } ``` ```c list_sort(&list_less); ``` 持續重複此流程,直到 `list_empty(head)` 為 `true` ,返回 `return` 1. ```graphviz digraph { rankdir=LR node [shape="block"] node4 [label="4"] node3 [label="3"] node2 [label="2"] null0 [shape=none, label="NULL"] head [shape=none, label="head"] pivot [shape=none, label="pivot"] pivot->node4 head->node4->node3->node2->null0 {rank=same;node4 pivot} } ``` 2. ```graphviz digraph { rankdir=LR node [shape="block"] node4 [label="4"] node3 [label="3"] node2 [label="2"] null0 [shape=none, label="NULL"] head [shape=none, label="head"] pivot [shape=none, label="pivot"] pivot->node4 head->node3->node2->null0 } ``` 3. ```graphviz digraph { rankdir=LR node [shape="block"] node4 [label="4"] node3 [label="3"] node2 [label="2"] null0 [shape=none, label="NULL"] head [shape=none, label="head"] pivot [shape=none, label="pivot"] list_less [shape=none,label="list_less"] list_geater [shape=none,label="list_geater"] empty1[label=""][shape=plaintext] empty2[label=""][shape=plaintext] head->empty1 list_geater->empty2 pivot->node4 list_less->node3->node2->null0 } ``` ```c list_add(&pivot->list, head); list_splice(&list_less, head); list_splice_tail(&list_greater, head); ``` 先將指定的 `pivot` 插入 linked list `head` 的開頭,再將 `list_less` 的所有 node 都插入到 `head` 的開始,然後把 `list_greater` 的所有 node 都插入到 `head` 的 結束位置中, `FFF` 則為 `list_splice_tail`。 1. ```graphviz digraph { rankdir=LR node [shape="block"] node2 [label="2"] head [shape=none, label="head"] head->node2 } ``` 2. ```graphviz digraph { rankdir=LR node [shape="block"] node3 [label="3"] node2 [label="2"] head [shape=none, label="head"] head->node2->node3 } ``` 3. ```graphviz digraph { rankdir=LR node [shape="block"] node4 [label="4"] node3 [label="3"] node2 [label="2"] head [shape=none, label="head"] head->node2->node3->node4 } ``` 4. ```graphviz digraph { rankdir=LR node [shape="block"] node5 [label="5"] node4 [label="4"] node3 [label="3"] node2 [label="2"] head [shape=none, label="head"] head->node2->node3->node4->node5 } ``` :::success **延伸問題** - [x] 解釋程式碼運作原理 - [ ] 針對 [Quick sort](https://en.wikipedia.org/wiki/Quicksort) 提出程式碼的改進方案並實作 - [ ] 引入 hybrid sort 策略並在 Linux 核心風格的 circular doubly-linked list 實作,需要量化性能表現並探討不同資料集 (data set) 的影響 - [ ] BONUS: 嘗試對 Linux 核心提出排序程式碼改進的貢獻 ::: ## 回答測驗二 ```c #define MAX_DEPTH 512 ``` ```c struct list_head stack[MAX_DEPTH]; for (int i = 0; i < MAX_DEPTH; i++) INIT_LIST_HEAD(&stack[i]); ``` 我們可以看見測驗2指定了`MAX_DEPTH = 512`的`stack`,用來模擬遞迴的功能,並用了 `INIT_LIST_HEAD` 針對 `stack` 中每一部分進行初始化,從上至下依序為 `stack[0], stack[1], stack[2] ...stack[MAX_DEPTH-1]`。 ```c int top = -1; list_splice_init(head, &stack[++top]); ``` 將 `head` 的所有 `node` 都插入到 `stack[0]` 的開始位置中 ```c int top = -1; list_splice_init(head, &stack[++top]); ``` ```c while (top >= 0) { INIT_LIST_HEAD(&partition); list_splice_init(&stack[top--], &partition); ``` 此時 `top` 為 `0` 符合進入`while loop` 的條件,將剛存進 `stack[0]` 的 `node` 都插入到 `partition` 的開始位置中,接著再將 `top` 減一,使 `top` 為 `-1`。 ```c 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); ``` 我們可以得知這時的 `partition` 已符合 `if` 的條件式,我們用 `pivot` 指向 `partition` 中的第一個 `node`,再用 `list_del` 把 `pivot` 所指向的 `node` 從 `partition` 取出來並初始化。 ```c 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); } ``` 從上方程式碼可以判斷出, `GGGG` 為 `list_for_each_entry_safe ` 用來 iterate `partition` 中的 `node`。 * 用 `list_del` 取出每一個 `node` * 接著將其和 `pivot` 進行 `cmpint` * 若是 `node` 小於 `pivot`,便會將該 `node` 移動到另一個 linked list `list_less` 的開頭 * 若是 `node` 大於 `pivot`,便會將該 `node` 移動到另一個 linked list `list_greater` 的開頭 ```c HHHH (&pivot->list, &list_less); ``` * 從上方可判斷出 `HHHH` 為 `list_move_tail` 。 結束 `interation` 後,使用 `list_move_tail` 把 `pivot` 移至 `list_less` 的尾端 ```c if (!list_empty(&list_greater)) list_splice_tail(&list_greater, IIII); if (!list_empty(&list_less)) list_splice_tail(&list_less, JJJJ); ``` * 我們從 `list_splice_tail` 得知, `IIII` 和 `JJJJ` 為 `&stack[++top]` * 透過 `if` 來分別檢測 `list_greater` 和 `list_less` 是否為空。 * 使用 `list_splice_tail` 將 `list_greater` 放入 `stack[0]` * 使用 `list_splice_tail` 將 `list_less` 放入 `stack[1]` 由於符合 `while loop` 的條件,`while loop` 會持續運作直到 `if (!list_empty(&partition) && !list_is_singular(&partition)) {` 無法被滿足,這時候 `stack` 會呈現由大至小頭部向尾部的分佈,也就是 `stack` 頂端僅存一個 `node` ,便跳至 `else`,也就是下方程式碼 ```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); } ``` * 先針對 `top` 累加一次 * 並對剛才只含有一個 `node` 的 `partition` 放回至 `stack[top]` 的開頭位置中 * 接下來在 `while loop`中使用 `list_del` 把 `tmp` 從 `stack[top]` 中移開,並放至 `head` 中,我們可以知道 `KKKK` 為 `stack[top--]`,將被移走 'node' 的 `stack[top]` 進行初始化,並把 `top` 減一 * 結束 `while loop` 後,可以在 `head` 中發現排序後的結果 :::success 延伸問題: 1. 解釋上方程式碼運作原理,指出設計和實作的缺失 2. 比較測驗 1 和測驗 2 的程式碼,設計實驗來分析二者效能,解釋為何非遞迴的實作未能總是比遞迴的實作快 3. 提出效能改進方案,特別是避免依賴 MAX_DEPTH 4. 引入 Introsort,也就是 quick sort 和 heap sort (或其他可避免前者 O(n2) 最差時間複雜度的演算法)。加入 heapsort 的原因為,當 quicksort 迭代 2∗log(n) 次後還排序完成,那就很可能會得到 O(n2) 時間複雜度。此時切換為 heap sort 能將其時間複雜度控制在 O(nlogn) ::: ## 回答測驗三