---
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
```