---
tags: linux kernel class
---
# 2023q1 Homework1 (quiz1)
contributed by < `willwillhi1` >
### 測驗一
```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```,然後調整數列,使得「所有在 ```pivot``` 左邊的數,都比 ```pivot``` 還小」,而「在 ```pivot``` 右邊的數都比 ```pivot``` 大」。
2. 接著,將所有在 ```pivot``` 左邊的數視為「新的數列」,所有在 ```pivot``` 右邊的數視為「另一個新的數列」,「分別」重複上述步驟(選 ```pivot```、調整數列),直到分不出「新的數列」為止。
```graphviz
digraph G {
        compound=true
    node[shape=record, height=.1];input result 
    divide_41 divide_21 divide_31 divide_11 divide_12 divide_13 divide_14
    merge_21 merge_41 merge_42 merge_11 merge_12 merge_13 merge_14 merge_15 merge_16 merge_17 merge_18 merge_19 merge_110 merge_111 merge_31;
    
    input[label="<f0>5|<f1>2|<f2>4|<f3>6|<f4>8|<f5>1|<f6>7|<f7>3"]
    result[label="<f0>1|<f1>2|<f2>3|<f3>4|<f4>5|<f5>6|<f6>7|<f7>8"]
    divide_41[label="<f1>2|<f2>4|<f3>1|<f4>3"]
    divide_31[label="<f1>6|<f2>8|<f3>7"]
    divide_11[label="<f0>1"]
    divide_21[label="<f0>4|<f1>3"]
    divide_12[label="<f0>3"]
    divide_13[label="<f0>8"]
    divide_14[label="<f0>7"]
    merge_21[label="<f0>3|<f1>4"]
    merge_11[label="<f0>1"]
    merge_12[label="<f0>2"]
    merge_13[label="<f0>5"]
    merge_14[label="<f0>6"]
    merge_15[label="<f0>8"]
    merge_16[label="<f0>7"]
    merge_41[label="<f0>1|<f1>2|<f2>3|<f3>4"]
    merge_17[label="<f0>5"]
    merge_18[label="<f0>6"]
    merge_19[label="<f0>8"]
    merge_110[label="<f0>7"]
    merge_42[label="<f0>1|<f1>2|<f2>3|<f3>4"]
    merge_111[label="<f0>5"]
    merge_31[label="<f0>6|<f1>7|<f2>8"]
    
    subgraph cluster_0 {
        
        label="divide";
        
        divide_pad[label="----------------------", style=invis]
        divide_pad -> divide_21[style=invis]
        divide_pad -> divide_13[style=invis]
        input -> divide_41
        input -> divide_31
        divide_41 -> divide_21
        divide_41 -> divide_11
        divide_31 -> divide_13
        divide_31 -> divide_14
        divide_21 -> divide_12
    }
    divide_12 -> pad[style=invis]
    subgraph cluster_2 {
        label="merge";
        
        merge_pad[label="--------------------", style=invis]
        rank=same; merge_11; merge_12; merge_13; merge_14; merge_15; merge_16;  merge_21
        rank=same; merge_41; merge_17; merge_18; merge_19; merge_110
        rank=same; merge_111; merge_42; merge_31
        pad[label="----------------------", style=invis]
        pad -> merge_13[style=invis]
        pad -> merge_42[style=invis]
        pad -> merge_31[style=invis]
        //pad -> merge_17[style=invis]
        //merge_11 -> merge_12[style=invis]
        //merge_23 -> merge_pad[style=invis]
        //merge_pad -> result[style=invis]
        
        merge_11 -> merge_41
        merge_12 -> merge_41
        merge_21 -> merge_41
        merge_13 -> merge_17
        merge_15 -> merge_19
        merge_16 -> merge_110
        merge_14 -> merge_18
        merge_41 -> merge_42
        merge_17 -> merge_111
        merge_18 -> merge_31
        merge_19 -> merge_31
        merge_110 -> merge_31
        merge_42 -> result
        merge_111 -> result
        merge_31 -> result
    }
}
```
* 程式碼中的 ```list_less```, ```list_greater``` 分別代表第一步中的兩個佇列的 ```head```。
* 用 ```list_first_entry``` 拿原本佇列的第一個節點當作 ```pivot```,所以 ```AAA = item```, ```BBB = list```。
* 13 ~ 19 行就是要把原本的佇列通過與 ```pivot``` 比較分到 ```list_less```, ```list_greater``` 之中,所以要用 ```CCC = list_for_each_entry_safe``` 來走訪整個佇列
    * 若節點的值小於 ```pivot```,就用 ```DDD = list_move_tail``` 將該節點從原佇列移動到 ```list_less``` 的尾端。
    * 反之若大於等於 ```pivot```,就用 ```EEE = list_move_tail``` 將改節點移動到 ```list_greater``` 的尾端。
* 分出 ```list_less``` 和 ```list_greater``` 後,就是進入第二步,重複的對其做 ```list_sort``` 直到分不出新的佇列。
* 最後 24 ~ 26 就是把 ```list_less```, ```pivot```, ```list_greater``` 接起來,所以 ```FFF = list_splice_tail``` 
### 測驗二
```c=
#define MAX_DEPTH 512
void q_sort(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);
    LIST_HEAD(partition);
    while (top >= 0) {
        INIT_LIST_HEAD(&partition);
        // list_splice_init(&stack[top--], &partition);
        if (!list_empty(&stack[top]) && !list_is_singular(&stack[top])) {
            list_splice_init(&stack[top--], &partition);
            struct list_head list_less, list_greater;
            INIT_LIST_HEAD(&list_less);
            INIT_LIST_HEAD(&list_greater);
            element_t *pivot = list_first_entry(&partition, element_t, list);
            list_del(&pivot->list);
            INIT_LIST_HEAD(&pivot->list);
            element_t *itm = NULL, *is = NULL;
            list_for_each_entry_safe (itm, is, &partition, list) {
                // list_del(&itm->list);
                if (strcmp(itm->value, pivot->value) < 0)
                    list_move(&itm->list, &list_less);
                else
                    list_move(&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])) {
                // element_t *tmp = list_first_entry(&stack[top], element_t, list);
                // list_del(&tmp->list);
                // INIT_LIST_HEAD(&stack[top--]);
                // list_add_tail(&tmp->list, head);
                list_splice_tail_init(&stack[top--], head);
            }
        }
    }
}
```
```struct list_head stack[MAX_DEPTH]```是用來模擬遞迴的操作,先對每一層做初始化 ```INIT_LIST_HEAD(&stack[i])```,```top``` 從 -1 開始,然後 ```list_splice_init(head, &stack[++top])``` 把 ```head``` 的資料移到 ```stack[0]```,接著底下這兩行
```c=
struct list_head partition;
INIT_LIST_HEAD(&partition);
```
可以用以下代替
```c=
LIST_HEAD(partition);
```
進入 ```while``` 迴圈後,
把 ```stack[top]``` 目前的資料移到 ```partition```,並初始化 ```stack[top]```
因為 ```stack[top]``` 目前的資料被拿出來了,所以 ```top``` 要減一。
```c=
INIT_LIST_HEAD(&partition);
list_splice_init(&stack[top--], &partition);
```
* 發現可以把上述的 
```c=
list_splice_init(&stack[top--], &partition);
```
加入到底下的 ```if``` 內,所以 ```if``` 的判斷式的 ```&partition``` 要改成 ```&stack[top]```,然後就可以不執行 ```else``` 裡的:
```c=
top++;
list_splice_tail(&partition, &stack[top]);
```
回到 ```if``` 在做的事,如果 ```partition``` 如果不是空或者只有一點,就把 ```partition``` 依照 ```pivot``` 分成 ```list_less``` 和 ```list_greater``` 兩個佇列。其中 ```list_del(&itm->list)``` 是多餘的,後面的 ```list_move``` 已經包含相同的效果。
```c=
if (!list_empty(&partition) && \
    !list_is_singular(&partition))
```
接著把 ```pivot``` 接到 ```list_less``` 尾端,
然後判斷如果 ```list_greater``` 不為空,就把它加到 ```stack[++top]``` 裡,```list_less``` 同理,
所以 ```stack``` 會先放大的再放小的。
```c=
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]);
```
如果 `partition` 為空或者只有一個節點且 `stack[top]` 只有一節點,就把當前的 `stack[top]` 的一個節點加到 `head` 的尾端,所以可以把下面 `while` 的四行用 `list_splice_tail_init` 代替。
```c=
else {
    while (top >= 0 && list_is_singular(&stack[top])) {
        // element_t *tmp = list_first_entry(&stack[top], element_t, list);
        // list_del(&tmp->list);
        // INIT_LIST_HEAD(&stack[top--]);
        // list_add_tail(&tmp->list, head);
        list_splice_tail_init(&stack[top--], head);
    }
```
### 效能比較
以下為用 qtest time 做的測試,每筆分別做五次
* recursion
    * 10000 筆: 0.013, 0.016, 0.006, 0.006, 0.007
    * 100000 筆: 0.126, 0.139, 0.125, 0.124, 0.122
    * 200000 筆: 0.282, 0.287, 0.280, 0.287, 0.202
    * 300000 筆: 0.461, 0.474, 0.455, 0.448, 0.463
    * 400000 筆: 0.631, 0.648, 0.624, 0.619, 0.626
    * 500000 筆: 0.834, 0.815, 0.795, 0.793, 0.729
    * 600000 筆: 1.010, 0.873, 1.011, 1.053, 0.922
    * 700000 筆: 1.223, 1.151, 1.040, 1.237, 1.237
    * 800000 筆: 1.409, 1.198, 1.153, 1.223, 1.349
    * 900000 筆: Time limit exceeded
* iteration
    * 10000 筆: 0.008, 0.009, 0.009, 0.010, 0.010
    * 100000 筆: 0.172, 0.149, 0.153, 0.149, 0.158
    * 200000 筆: 0.341, 0.35, 0.34, 0.344, 0.34
    * 300000 筆: 0.567, 0.541, 0.508, 0.588, 0.551
    * 400000 筆: 0.799, 0.792, 0.788, 0.742, 0.791
    * 500000 筆: 1.077, 1.016, 0.952, 0.995, 0.915
    * 600000 筆: 1.280, 1.175, 1.222, 1.214, 1.203
    * 700000 筆: Time limit exceeded
### 用 perf 做測試
首先建立一個測試用的 cmd,然後用以下指令做測試
```c=
sudo perf stat -e cache-misses,cache-references,instructions,cycles ./qtest -f sort.cmd
```
```c=
will@will:~/linux2023/lab0-c$ cat sort.cmd 
option fail 0
option malloc 0
new
ih RAND 10000
sort
free
```
* recursion
    ```c=
     Performance counter stats for './qtest -f sort.cmd':
                76,501      cache-misses              #    6.726 % of all cache refs    
             1,137,407      cache-references                                            
            45,705,227      instructions              #    1.56  insn per cycle         
            29,351,662      cycles                                                      
           0.018867434 seconds time elapsed
           0.014206000 seconds user
           0.004735000 seconds sys
    ```
* iteration
    ```c=
     Performance counter stats for './qtest -f sort.cmd':
               139,941      cache-misses              #    9.913 % of all cache refs    
             1,411,675      cache-references                                            
            51,237,005      instructions              #    1.54  insn per cycle         
            33,255,167      cycles                                                      
           0.033091868 seconds time elapsed
           0.020052000 seconds user
           0.012031000 seconds sys
    ```