---
# System prepended metadata

title: 2025q1 Homework1 (lab0)

---

# 2025q1 Homework1 (lab0)
contributed by < [BennyWang1007](https://github.com/BennyWang1007) >

{%hackmd NrmQUGbRQWemgwPfhzXj6g %}

## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0

$ lscpu
架構：                    x86_64
  CPU 作業模式：          32-bit, 64-bit
  Address sizes:          39 bits physical, 48 bits virtual
  Byte Order:             Little Endian
CPU(s):                   20
  On-line CPU(s) list:    0-19
供應商識別號：            GenuineIntel
  Model name:             12th Gen Intel(R) Core(TM) i7-12700H
    CPU 家族：            6
    型號：                154
    每核心執行緒數：      2
    每通訊端核心數：      14
    Socket(s):            1
    製程：                3
    CPU(s) scaling MHz:   23%
    CPU max MHz:          4700.0000
    CPU min MHz:          400.0000
    BogoMIPS:             5376.00
Virtualization features:  
  虛擬：                  VT-x
Caches (sum of all):      
  L1d:                    544 KiB (14 instances)
  L1i:                    704 KiB (14 instances)
  L2:                     11.5 MiB (8 instances)
  L3:                     24 MiB (1 instance)
NUMA:                     
  NUMA 節點：             1
  NUMA node0 CPU(s)：     0-19
```

## 針對佇列操作的程式碼實作

### q_new
> Commit [1414f73](https://github.com/BennyWang1007/lab0-c/commit/1414f73beabb6d61d56c64da7411ac5ea088fefe)

此函式會創建一個全新的、初始為空的佇列，實作如下
1. 使用`malloc`申請一個`list_head`節點的空間，若申請失敗則返回`NULL`
2. 使節點的`prev`和`next`皆指向自己，滿足雙向鏈結串列的定義

### q_free
> Commit [1414f73](https://github.com/BennyWang1007/lab0-c/commit/1414f73beabb6d61d56c64da7411ac5ea088fefe)

此函式會移除並釋放佇列中的所有節點，實作過程如下
1. 首先判斷 head 是否為 `NULL`，若空則返回
2. 接著遍歷整個 `queue`，釋放將所有節點的 `value` 和節點本身

### q_insert_head
> Commit [a9ee6cc](https://github.com/BennyWang1007/lab0-c/commit/a9ee6cc99754b502d537a00f24799c977ef245a5)

此函式會在佇列的頭部插入一個元素，實作過程如下：

1. 若 head 為 `NULL`，則返回 `false`。
2. 使用 `malloc` 申請 `element_t` 節點的空間，若申請失敗則返回 `false`。
3. 初始化 `new_element` 節點。
4. 使用 `strdup()` 複製輸入字串 `s`，若複製失敗，則釋放 `new_element` 並返回 `false`。
5. 使用 `list_add` 將新節點加入 head。

### q_insert_tail
> Commit [a9ee6cc](https://github.com/BennyWang1007/lab0-c/commit/a9ee6cc99754b502d537a00f24799c977ef245a5)

此函式會在佇列的尾部插入一個元素，實作過程如下：

1. 若 head 為 `NULL`，則返回 `false`。
2. 使用 `malloc` 申請 `element_t` 節點的空間，若申請失敗則返回 `false`。
3. 初始化 `new_element` 節點。
使用 strdup 複製輸入字串 s，若 `strdup` 失敗則釋放 `new_element` 並返回 `false`。
4. 使用 `list_add_tail` 將新節點加入 head。

### q_remove_head
> Commit [edec651](https://github.com/BennyWang1007/lab0-c/commit/edec6510404ee291a14746eb3c86401a9ddc4f06)

此函式會從佇列的頭部移除一個元素，實作過程如下：

1. 若 head 為 `NULL` 或佇列為空，則返回 `NULL`。
2. 取得佇列的第一個元素 entry。
若 `sp` 和 `entry->value` 不為 `NULL`，則將 `entry->value` 複製到 `sp`，並確保字串以 `\0` 結尾。
3. 使用 `list_del_init` 從鏈結串列中移除該節點。
4. 返回 `entry`，由呼叫者負責釋放記憶體。

### q_remove_tail
> Commit [edec651](https://github.com/BennyWang1007/lab0-c/commit/edec6510404ee291a14746eb3c86401a9ddc4f06)

此函式會從佇列的尾部移除一個元素，實作過程如下：

1. 若 head 為 `NULL` 或佇列為空，則返回 `NULL`。
2. 取得佇列的最後一個元素 `entry`。
若 `sp` 和 `entry->value` 不為 NULL，則複製字串至 sp，確保字串結尾為 `'\0'`。
3. 使用 `list_del_init` 從鏈結串列中移除該節點。
4. 返回 `entry`，由呼叫者負責釋放記憶體。

### q_size
> Commit [6f82f18](https://github.com/BennyWang1007/lab0-c/commit/6f82f187d788dd7152631e071072bf2476bc0d00)

此函式會回傳佇列中的元素數量，實作過程如下：

1. 若 head 為 `NULL`，則返回 0。
使用變數 `count` 記錄節點數量，初始化為 `0`。
2. 使用 `for` 迴圈遍歷佇列，每遇到一個節點就將 `count` 增加 1。
3. 最後返回 `count` 值。

### q_delete_mid
> Commit [3048563](https://github.com/BennyWang1007/lab0-c/commit/3048563bb6fcf55dc75bf9b1b23f1479f7f29565)

此函式會刪除佇列的中間節點，實作過程如下：

1. 若 head 為 `NULL` 或佇列為空，則返回 `false`。
2. 使用兩個指標 `slow` 和 `fast`，`slow` 每次移動一步，而 `fast` 每次移動兩步，以此找到佇列的中間節點。
3. 當 `fast` 到達佇列尾部時，slow 會指向中間節點。
4. 使用 `list_del` 移除 `slow` 指向的節點，並釋放其 value 及節點本身的記憶體。
5. 返回 `true` 以表示刪除成功。

### q_delete_dup
> Commit [9d5bb17](https://github.com/BennyWang1007/lab0-c/commit/9d5bb173b17bbd1cd164c90af71fcdf0b2c92c1d)

此函式會刪除所有具有重複字串的節點，實作過程如下：

1. 若佇列為空，則直接返回 `true`。
2. 使用 `prev` 指向當前處理的節點，`prev_node` 指向 `prev` 的 `list_head`，`node` 用於走訪佇列。
3. 若當前節點與 `prev` 的 value 相同，則標記 `is_dup = true`，並刪除當前節點。
4. 若當前節點與 `prev` 不同，則檢查 `is_dup` 是否為 `true`，若是則刪除 `prev_node`，否則將 `prev` 更新為當前節點。
5. 走訪完成後，若 `is_dup` 為 `true`，則刪除最後一個重複節點。
6. 返回 `true` 代表刪除成功。

### q_swap
> Commit [5f13d84](https://github.com/BennyWang1007/lab0-c/commit/5f13d84caebba07aa35bd6a5cda6ccc9a402ea09)

此函式會交換佇列中每兩個相鄰的節點，實作過程如下：

1. 若佇列為空或只有一個節點，則不需進行交換，直接返回。
2. 使用 `node` 走訪佇列，每次處理兩個節點。
3. 透過調整指標，將 `node` 和 `node->next` 互換位置。
4. 更新 `node`，跳過剛剛交換的節點，繼續處理下一對相鄰節點。

### q_reverse
> Commit [1b7b7ad](https://github.com/BennyWang1007/lab0-c/commit/1b7b7ad7ae970422f82c89c07edd6510eec09801)

此函式會將佇列中的元素順序完全反轉，實作過程如下：

1. 若 head 為 `NULL`，則直接返回。
2. 使用 `node` 遍歷佇列，並在每個節點上交換 `prev` 和 `next` 指標。
3. 最後調整 head 的 `next` 和 `prev`，完成反轉。

> Commit [25bcd80](https://github.com/BennyWang1007/lab0-c/commit/25bcd80b966654c28dc5fa7d7ed0a42ff2033cf8)

此提交修正了原先實作雙向鏈結串列指標的錯誤，並將 `for` 迴圈改成 `while` 迴圈增加可讀性。

### q_reverseK
> Commit [e8c1632](https://github.com/BennyWang1007/lab0-c/commit/e8c16323738568ba14b354da1ead74fc8ec14891)

此函式會將佇列中的元素以 k 個節點為一組進行翻轉，實作過程如下：

1. 若 head 為 `NULL` 或對列為空，或 `k <= 1`，則直接返回。
2. 若佇列的節點數 `count < k` 則無需翻轉。
3. 設定整型變數 `round` 為 `count / k`，表示需要翻轉的區塊數。
4. 使用 `node` 作為翻轉起點，逐區塊進行處理：
    1. `cur` 為當前區塊的第一個節點。
    2. 使用 `list_move(cur, node);` 將節點移動到當前區塊的起點。
    3. 逐步翻轉 `k` 個節點後，將 `node` 移動到下一區塊的起點。

> Commit [61ae3a9](https://github.com/BennyWang1007/lab0-c/commit/61ae3a95c14bbeaa377f70ea7ebf7f194d8e0b77)

此提交優化了 `q_reverseK` 的效率：
1. 將 `list_move` 改成直接的 pointer 交換，免去多餘的雙向串列操作 (`list_del` + `list_add`)。
2. 將原本迭代 `k` 次的 `for` 迴圈改為直接設 `node = node->next;` ，大幅降低尋找下一個 group 的起點的操作數（ $O(k) \rightarrow O(1)$ ）。
```diff
void q_reverseK(struct list_head *head, int k)
{
    ...
    for (int i = 0; i < round; i++) {
        ...
        /* Reverse the nodes */
        for (int j = 0; j < k; j++) {
            next = cur->next;
-           list_move(cur, node);
+           tmp = cur->next;
+           cur->next = cur->prev;
+           cur->prev = tmp;
            cur = next;
        }
        /* Swap the pointers */
+       next->prev->prev = node;
+       node->next->next = next;
+       tmp = next->prev;
+       next->prev = node->next;
+       node->next = tmp;
        /* Move to the next group */
-       for (int j = 0; j < k; j++)
-           node = node->next;
+       node = next->prev;
    }
}
```

### q_sort
> Commit [38f7439](https://github.com/BennyWang1007/lab0-c/commit/38f7439d66a67415945096668a3d996af38ad96e)

此函式會對佇列中的元素進行排序，並可選擇升冪或降冪排序，實作過程如下：

透過 `merge_sort_ascend` 使用 **合併排序** 演算法對佇列進行升冪排序。
若 `descend` 為 `true`，則呼叫 `q_reverse` 進行反轉，使佇列變為降冪排序。

### merge_sort_ascend
> Commit [38f7439](https://github.com/BennyWang1007/lab0-c/commit/38f7439d66a67415945096668a3d996af38ad96e)

此函式使用合併排序將佇列 **升冪排序**：

1. 若佇列為空或只有一個元素，則直接返回。
2. 使用 **快慢指標** 找到佇列的 **中間節點**，將佇列拆分為左右兩半。
3. 透過遞迴呼叫 `merge_sort_ascend` 分別對 `left` 和 `right` 排序。
4. 使用 `merge` 合併 `left` 和 `right`，將結果存入 head。

### merge
> Commit [38f7439](https://github.com/BennyWang1007/lab0-c/commit/38f7439d66a67415945096668a3d996af38ad96e)

此函式負責合併兩個已排序的子串列 l 和 r，並將結果存入 head：

1. 比較 l 和 r 首節點的 value，將較小的節點移至 head 的尾部。
2. 當 l 或 r 中有節點未處理完時，直接將剩餘的節點拼接到 head。

此函式是參考去年作業< `LIAO-JIAN-PENG` > 的[架構](https://github.com/LIAO-JIAN-PENG/lab0-c/blob/master/queue.c#L265)，在該實作之上我將該程式碼的分支簡化，提昇效率和品味。又因 `list_splice_tail_init` 內就會判斷是否為空，因此可以將 `if` 判斷式移除。

```diff
void merge(struct list_head *head, struct list_head *l, struct list_head *r)
{
    /* Merge the two sorted lists left and right into head */
    while (!list_empty(l) && !list_empty(r)) {
        char *l_val = list_first_entry(l, element_t, list)->value;
        char *r_val = list_first_entry(r, element_t, list)->value;
-       if (strcmp(l_val , r_val) < 0) {
-           list_move_tail(l->next, head);
-       } else {
-           list_move_tail(r->next, head);
-       }
+       struct list_head *node = strcmp(l_val, r_val) <= 0 ? l->next : r->next;
+       list_move_tail(node, head);
    }

-   if (!list_empty(l))
-       list_splice_tail_init(l, head);
-   if (!list_empty(r))
-       list_splice_tail_init(r, head);
    /* Move the remaining elements to head */
+   list_splice_tail_init(l, head);
+   list_splice_tail_init(r, head);
}
```

### q_ascend
> Commit [2008bc4](https://github.com/BennyWang1007/lab0-c/commit/2008bc4cf88cb1aa1e2d5a1f2f9745619390a2b1)

此函式會 移除所有非遞增的節點，確保佇列中的值嚴格遞增，具體實作過程如下：

1 若佇列為空或僅有一個節點，則直接返回對應大小。
2. 從 **倒數第二個節點開始** 向前走訪佇列，並維護一個 `min` 變數，記錄目前遇到的最小值。
3. 若當前節點的值 小於 `min`，則更新 `min`，保留該節點；否則刪除該節點。
4. 最後返回佇列剩餘的節點數量。

此函式是參考去年作業< `LIAO-JIAN-PENG` > 的架構，但做了兩個效能上的優化：
1. 避免兩次 `q_reverse` 操作：
    * 原實作先反轉鏈結串列，然後使用 `list_for_each_safe` 走訪刪除不符合條件的節點，最後再反轉回來，這樣多做了兩次 `O(n)` 的反轉操作。
    * 優化後直接從尾端開始走訪，省去了 `q_reverse` 的額外開銷。
2. 刪除多餘的 `NULL` 判斷：
    * 原實作在走訪過程中皆需判斷 `min` 是否為 `NULL`。
    * 優化後我直接將 `min` 初始化為最後一個節點的值 `(head->prev)`，避免了多餘的 `NULL` 判斷，減少不必要的條件分支，並從倒數第二個節點開始走訪。
```diff=
int q_ascend(struct list_head *head)
{
-   q_reverse(head);
    ...
-   char *min = NULL;
+   const char *min = list_entry(node, element_t, list)->value;
    ...
-       if (!min || strcmp(e->value, min) < 0)
+       if (strcmp(e->value, min) < 0)
    ...
-   q_reverse(head);
    return q_size(head);
}
```

### q_descend
> Commit [2008bc4](https://github.com/BennyWang1007/lab0-c/commit/2008bc4cf88cb1aa1e2d5a1f2f9745619390a2b1)

此函式會 移除所有非遞減的節點，確保佇列中的值是嚴格遞減的，具體實作過程如下：

1. 若佇列為空或僅有一個節點，則直接返回對應大小。
2. 從 **倒數第二個節點開始** 向前走訪佇列，並維護一個 `max` 變數，記錄目前遇到的最大值。
3. 若當前節點的值 大於 `max`，則更新 `max`，保留該節點；否則刪除該節點。
4. 最後返回佇列剩餘的節點數量。

### q_merge
> Commit [77eba2e](https://github.com/BennyWang1007/lab0-c/commit/77eba2e366ac057021ad8d5c514a16295adccb18)

此函式的作用是 合併所有已排序的佇列，並按照指定的升序或降序排列，具體實作過程如下：

1. 若 `head` 為空，則返回 0。
2. 取得 `head` 中的第一個 `queue_contex_t` 作為合併的主要佇列 `qc`。
3. 走訪 `head` 中的其他 `queue_contex_t`，將其佇列 (`q`) 併入 `qc->q`，並更新 `size`。
4. 併入後，對 `qc->q` 進行排序。
5. 最後返回合併後的佇列大小。

> Commit [f656bf8](https://github.com/BennyWang1007/lab0-c/commit/f656bf8987026b408399fb3f5cdbd4c8eecf07fa)

此提交優化了：
1. 修正變數名稱，增加可讀性。（`qc` → `first_qc`, `tmp` → `cur_qc`）
2. 不使用 `list_for_each_entry` ，顯式地從第二個元素開始走訪，優化掉了迭代中的 `if` 判斷。

```diff
int q_merge(struct list_head *head, bool descend)
{
    // https://leetcode.com/problems/merge-k-sorted-lists/
-    queue_contex_t *qc = list_first_entry(head, queue_contex_t, chain);
-    queue_contex_t *tmp = NULL;
+    queue_contex_t *first_qc = list_first_entry(head, queue_contex_t, chain);
+    struct list_head *cur_chain = (&(first_qc->chain))->next;

-    list_for_each_entry (tmp, head, chain) {
+    for (; cur_chain != head; cur_chain = cur_chain->next) {
-        if (tmp == qc)
-            continue;
+        queue_contex_t *cur_qc = list_entry(cur_chain, queue_contex_t, chain);
        ...
    }
    ...
}
```

## Dudect 修正
> Commit [a3405ea](https://github.com/BennyWang1007/lab0-c/commit/a3405eac8db3b8fe9c369ed0ca5ab2606a3a3bd4)

1. 首先我先準備 `prepare_percentiles()` 函式來生成指數遞減的不同的取樣域值： $threshold(x)=1-{0.5}^{\dfrac{10\ \cdot\ x}{NUM\_PERCENTILES}}$ ，此處 $NUM\_PERCENTILES$ 取 $100$，可得以下曲線。
![Dudect_thredsholds](https://hackmd.io/_uploads/ryD7zXOi1g.png)

2. 接著在執行的過程中，使用不同的區間來進行 Welch's t-test，藉由取不同區間段的資料來排除掉極端值，或是因被 CPU 排程影響的異常值。
```c
// t-test on cropped execution times, for several cropping thresholds.
for (size_t j = 0; j < NUM_PERCENTILES; j++) {
    if (difference < percentiles[j]) {
        t_push(t, difference, classes[i]);
    }
}
```

## Shuffle
### 實作 **Fisher–Yates shuffle** 演算法：
> Commit [c67bb6e](https://github.com/BennyWang1007/lab0-c/commit/c67bb6ed7f0852e3110aa5a6e72fa1b05a74e1ad)

```c
    /* Fisher-Yates shuffle */
    for (size_t i = qsize - 1; i > 0; i--) {
        size_t j = rand() % (i + 1);
        struct list_head *cur = head->next, *swap = head->next;
        for (size_t k = 0; k < i; k++)
            cur = cur->next;
        for (size_t k = 0; k < j; k++)
            swap = swap->next;
        swap_nodes(swap, cur);
    }
```
從尾部開始走訪，從該節點之前（包含）隨機抽取一個點進行互換。
### 機率計算
考慮佇列包含 $n$ 個節點，記 $a_1, a_2, ..., a_n$。
$a_n$為隨機與所有元素調換，故其值為$a_1-a_n$之機率皆為$\dfrac{1}{n}$。
假設從 $a_{k+1}-a_n$ 的所有點成為任一點的機率皆為$\dfrac{1}{n}$，則點 $a_1-a_k$ 有 $\dfrac{1}{n}$ 成為 $a_n$、$\dfrac{1}{n-1}\cdot\dfrac{n-1}{n}=\dfrac{1}{n}$ 的機率成為 $a_{n-1}$、$\dfrac{1}{n-2}\cdot\dfrac{n-2}{n}=\dfrac{1}{n}$ 的機率成為 $a_{n-2}$，...。
此時點$a_k$成為$a_{k-1}$的機率為$\dfrac{1}{k}\cdot\dfrac{n-(n-k)}{n}=\dfrac{1}{n}$，．．．，成為$a_1$的機率為 $\dfrac{1}{n}$。
因此$a_k$成為任一點的機率也皆為$\dfrac{1}{n}$。
根據數學歸納法，$a_n$成為任一點的機率皆為$\dfrac{1}{n}\Rightarrow$所有點成為任一點的機率皆為$\dfrac{1}{n}$。

### Shannon entropy
根據上述推導與熵的公式：$H=-\sum{P(p_i)\cdot log(P(p_i))}$，此演算法的亂度$H=-\displaystyle\sum_{i=1}^{n!}{\frac{1}{n!}\cdot log(\frac{1}{n!})}=log(n!)$ 為理論最大亂度。（$n!$ 種排列，機率各$\dfrac{1}{n!}$）

### 模擬測試
由[測試用的 Python 腳本](https://hackmd.io/@sysprog/linux2025-lab0-d#%E6%B8%AC%E8%A9%A6%E7%A8%8B%E5%BC%8F) `shuffle_test.py` 測試，得出以下結果以及作圖。
```sh
Expectation:  41666
Observation:  {'1234': 41894, '1243': 41595, '1324': 41504, '1342': 41843, '1423': 41723, '1432': 41952, '2134': 41567, '2143': 41137, '2314': 41565, '2341': 41480, '2413': 41757, '2431': 41664, '3124': 41991, '3142': 41419, '3214': 41515, '3241': 41999, '3412': 41906, '3421': 41648, '4123': 41547, '4132': 41873, '4213': 41349, '4231': 41656, '4312': 41730, '4321': 41686}
chi square sum:  25.505448087169388
```
![Figure_shuffle_result](https://hackmd.io/_uploads/HkqaVNPj1x.png)
![Chi-square_sheet](https://hackmd.io/_uploads/BJLNUEPi1g.png)

從 $\chi^2=25.5$ 以及上圖卡方分佈在 $df=23$ 時的表可得 $p_{value} > 0.2$，因此不拒絕虛無假設 $\Rightarrow$ 此 Shuffle 方法是均勻分佈的。

## Linux List Sorting
> Commit [940cc05](https://github.com/BennyWang1007/lab0-c/commit/94895e1213605c5d31711e9cc872210d244f735b)

我將 Linux kernel 中的 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 移植到 `linux_listsort.c` 中，並在 `qtest` 內新增指令 `sort_l` 以供測試。

> Commit [f0147c7](https://github.com/BennyWang1007/lab0-c/commit/f0147c79cf2f1f54a584d534a3859375d0e167b2)

並撰寫測試程式 `sort_eff_read.c` (讀取測試資料)、`sort_eff_qsort.c` (執行q_sort) 以及 `sort_eff_linux.c` (執行Linux listsort)、 配合 `clock()` 和以下 `perf` 指令進行測試。
```shell
$ perf stat ./test_filename
```
得出以下輸出：
```perf
Read testcases took  4.838672 sec
q_sort         took 18.697750 sec
Linux sort     took  8.900504 sec
```

```perf
Read test cases took 4.838672 sec

 Performance counter stats for './sort_eff_read':

          5,043.21 msec task-clock                       #    1.000 CPUs utilized             
                48      context-switches                 #    9.518 /sec                      
                 4      cpu-migrations                   #    0.793 /sec                      
         2,109,444      page-faults                      #  418.274 K/sec                     
     9,694,053,542      cpu_atom/cycles/                 #    1.922 GHz                         (0.00%)
    23,277,332,448      cpu_core/cycles/                 #    4.616 GHz                         (100.00%)
     5,632,706,802      cpu_atom/instructions/           #    0.58  insn per cycle              (0.00%)
    62,900,097,052      cpu_core/instructions/           #    6.49  insn per cycle              (100.00%)
     1,114,237,134      cpu_atom/branches/               #  220.938 M/sec                       (0.00%)
    12,711,879,975      cpu_core/branches/               #    2.521 G/sec                       (100.00%)
        50,854,925      cpu_atom/branch-misses/          #    4.56% of all branches             (0.00%)
        12,050,810      cpu_core/branch-misses/          #    1.08% of all branches             (100.00%)
             TopdownL1 (cpu_core)                 #     31.0 %  tma_backend_bound      
                                                  #      2.0 %  tma_bad_speculation    
                                                  #     20.0 %  tma_frontend_bound     
                                                  #     47.1 %  tma_retiring             (100.00%)
             TopdownL1 (cpu_atom)                 #     41.9 %  tma_bad_speculation    
                                                  #     13.6 %  tma_retiring             (0.00%)
                                                  #      0.0 %  tma_backend_bound      
                                                  #     44.5 %  tma_frontend_bound       (0.00%)

       5.045026600 seconds time elapsed

       3.029803000 seconds user
       2.013869000 seconds sys


q_sort took 18.697750 sec

 Performance counter stats for './sort_eff_qsort':

         23,617.91 msec task-clock                       #    0.999 CPUs utilized             
               882      context-switches                 #   37.345 /sec                      
                43      cpu-migrations                   #    1.821 /sec                      
         2,109,442      page-faults                      #   89.315 K/sec                     
    80,433,036,134      cpu_atom/cycles/                 #    3.406 GHz                         (0.17%)
   108,092,876,794      cpu_core/cycles/                 #    4.577 GHz                         (99.77%)
    46,758,971,684      cpu_atom/instructions/           #    0.58  insn per cycle              (0.20%)
    89,704,983,258      cpu_core/instructions/           #    1.12  insn per cycle              (99.77%)
     8,907,805,711      cpu_atom/branches/               #  377.163 M/sec                       (0.20%)
    17,480,987,840      cpu_core/branches/               #  740.158 M/sec                       (99.77%)
       198,552,105      cpu_atom/branch-misses/          #    2.23% of all branches             (0.20%)
       213,863,677      cpu_core/branch-misses/          #    2.40% of all branches             (99.77%)
             TopdownL1 (cpu_core)                 #     67.2 %  tma_backend_bound      
                                                  #      3.1 %  tma_bad_speculation    
                                                  #     10.9 %  tma_frontend_bound     
                                                  #     18.8 %  tma_retiring             (99.77%)
             TopdownL1 (cpu_atom)                 #     11.2 %  tma_bad_speculation    
                                                  #     12.3 %  tma_retiring             (0.20%)
                                                  #     69.3 %  tma_backend_bound      
                                                  #      7.2 %  tma_frontend_bound       (0.20%)

      23.646281130 seconds time elapsed

      21.631719000 seconds user
       1.986239000 seconds sys


Linux sort took 8.900504 sec

 Performance counter stats for './sort_eff_linux':

         13,850.40 msec task-clock                       #    0.996 CPUs utilized             
             1,606      context-switches                 #  115.953 /sec                      
                 9      cpu-migrations                   #    0.650 /sec                      
         2,109,444      page-faults                      #  152.302 K/sec                     
    48,078,089,762      cpu_atom/cycles/                 #    3.471 GHz                         (0.05%)
    63,453,990,816      cpu_core/cycles/                 #    4.581 GHz                         (99.93%)
   114,601,258,673      cpu_atom/instructions/           #    2.38  insn per cycle              (0.06%)
    84,023,936,486      cpu_core/instructions/           #    1.75  insn per cycle              (99.93%)
    23,391,891,100      cpu_atom/branches/               #    1.689 G/sec                       (0.06%)
    17,286,557,130      cpu_core/branches/               #    1.248 G/sec                       (99.93%)
        26,820,239      cpu_atom/branch-misses/          #    0.11% of all branches             (0.06%)
       406,362,795      cpu_core/branch-misses/          #    1.74% of all branches             (99.93%)
             TopdownL1 (cpu_core)                 #     29.5 %  tma_backend_bound      
                                                  #      4.2 %  tma_bad_speculation    
                                                  #     22.2 %  tma_frontend_bound     
                                                  #     44.1 %  tma_retiring             (99.93%)
             TopdownL1 (cpu_atom)                 #      2.1 %  tma_bad_speculation    
                                                  #     53.7 %  tma_retiring             (0.06%)
                                                  #     21.2 %  tma_backend_bound      
                                                  #     22.9 %  tma_frontend_bound       (0.06%)

      13.908267131 seconds time elapsed

      11.717652000 seconds user
       2.127757000 seconds sys
```

注：以下皆以 **Linux 實作** 與 **原版** 代稱 `Linux listsort` 與 `q_sort` 。
比較perf輸出可發現：
1. 可以發現 Linux 實作的效率比原版高非常多，Linux 實作的效率是原板的$\dfrac{8.90}{18.70}=2.10$倍。
2. Linux 實作的 instructions/cycle 比原版高（1.75 > 1.12），這是因為 Linux 實作中，merge sort 的 bottom up 實作對 cache 較友善。
3. Linux 實作的 branch miss 也比原版的低，可能是因為 Linux 實作比傳統的 bottom-up mergesort少了 $0.2^n$ 個比較，並且Linux實作少了很多檢查（如頭是否為空），讓CPU更容易預測分支。

> Commit [cca2af4](https://github.com/BennyWang1007/lab0-c/commit/cca2af408fc3779e12ec161ec743dfae28c41b22)

此提交將 `sort_eff_qsort.c`、`sort_eff_linux.c`以及`sort_eff_read.c `合併到`sort_eff.c`中，減少程式碼重複性並增加維護性。

可以執行以下命令測試
```shell
$ make sort_eff
$ ./sort_eff [linux, qsort, read] [-wait]
```

### 嘗試改進
首先我先用 `perf top` 取樣：
```shell
$ perf top -p pid
```

q_sort：
```perf
  44.30%  sort_eff_qsort  [.] merge
  34.06%  libc.so.6       [.] __strcmp_avx2
  10.86%  sort_eff_qsort  [.] q_reverse
   8.27%  sort_eff_qsort  [.] merge_sort_ascend
   0.57%  sort_eff_qsort  [.] strcmp@plt
   ...
```

Linux listsort：
```perf
  56.03%  libc.so.6       [.] __strcmp_avx2
  19.15%  sort_eff_linux  [.] cmp_descend
  16.85%  sort_eff_linux  [.] linux_merge
   2.94%  sort_eff_linux  [.] list_sort
   1.67%  sort_eff_linux  [.] strcmp@plt
   1.03%  sort_eff_linux  [.] merge_final
   ...
```

以及用 `perf record` 取樣並配合 `perf report`（篩選過後）
q_sort：
```perf
+   34.06%    33.48%  sort_eff_qsort  sort_eff_qsort     [.] merge
+   29.52%    28.88%  sort_eff_qsort  libc.so.6          [.] __strcmp_avx2
+   17.97%     1.60%  sort_eff_qsort  sort_eff_qsort     [.] alloc
+   11.63%     0.80%  sort_eff_qsort  libc.so.6          [.] malloc
+   11.44%     3.65%  sort_eff_qsort  libc.so.6          [.] _int_malloc
+    6.91%     6.88%  sort_eff_qsort  sort_eff_qsort     [.] merge_sort_ascend
+    6.38%     6.37%  sort_eff_qsort  sort_eff_qsort     [.] q_reverse
+    4.24%     4.03%  sort_eff_qsort  libc.so.6          [.] __random
+    1.95%     0.58%  sort_eff_qsort  sort_eff_qsort     [.] read_test_cases 
+    1.13%     0.59%  sort_eff_qsort  sort_eff_qsort     [.] strcmp@plt 
+    0.83%     0.74%  sort_eff_qsort  libc.so.6          [.] __random_r 
     0.25%     0.06%  sort_eff_qsort  sort_eff_qsort     [.] malloc@plt 
```

Linux listsort：
```perf
+   35.88%    35.09%  sort_eff_linux  libc.so.6          [.] __strcmp_avx2
+   29.43%     2.31%  sort_eff_linux  sort_eff_linux     [.] alloc
+   19.25%     1.29%  sort_eff_linux  libc.so.6          [.] malloc
+   18.89%     6.07%  sort_eff_linux  libc.so.6          [.] _int_malloc
+   14.71%    13.36%  sort_eff_linux  sort_eff_linux     [.] cmp_descend
+   12.97%    10.12%  sort_eff_linux  sort_eff_linux     [.] linux_merge
+    7.16%     6.83%  sort_eff_linux  libc.so.6          [.] __random
+    3.04%     0.84%  sort_eff_linux  sort_eff_linux     [.] read_test_cases
     1.70%     1.65%  sort_eff_linux  sort_eff_linux     [.] list_sort
+    1.55%     0.79%  sort_eff_linux  sort_eff_linux     [.] strcmp@plt
+    1.24%     1.03%  sort_eff_linux  libc.so.6          [.] __random_r
     0.75%     0.51%  sort_eff_linux  sort_eff_linux     [.] merge_final
     0.74%     0.62%  sort_eff_linux  libc.so.6          [.] __strlen_avx2
```
觀察：
1. 首先，`q_reverse` 佔了其中不小的比例，要提昇效率應該使用兩個不同的cmp函式而不是先排序成遞增再反轉。
2. 另外，發現在兩種實作方法中，Linux `merge`所佔的比例約只有q_sort 的 20% 。下圖為q_sort `merge`不同程式碼段佔的時間比例，可以發現在指標的指派上花了很多時間，可能是因為cache miss的關係。
3. 字串比較的時間也是 Linux 實作更快（雖然Linux看似比例較高，但整個程式花的時間差了兩倍以上，因此 Linux 還是更快），這源自於 Linux 實作時減少的比較數量。

```perf
       │    char *l_val = list_first_entry(l, element_t, list)->value;         ▒
       │    char *r_val = list_first_entry(r, element_t, list)->value;         ▒
       │    struct list_head *node = strcmp(l_val, r_val) <= 0 ? l->next : r->n◆
 23.41 │      mov     -0x8(%rbx),%rsi
 40.18 │      mov     -0x8(%rbp),%rdi
  1.40 │    → call    strcmp@plt
  0.15 │      test    %eax,%eax
  2.11 │      cmovle  %rbp,%rbx
       │    struct list_head *next = node->next;
 12.55 │      mov     0x8(%rbx),%rdx
       │    struct list_head *prev = node->prev;
  0.02 │      mov     (%rbx),%rax
       │    next->prev = prev;
 12.81 │      mov     %rax,(%rdx)
```

## Address Sanitizer
在執行 `make SANITIZER=1 && make test` 後，並沒有遇到錯誤訊息產生，完美通過 Address Sanitizer 的檢測。

## Valgrind
我寫了一個簡單的 shell script 來使用 Valgrind 測試是否有記憶體錯誤，並將 stdout 導到 `/dev/null` 使輸出更整潔：

```sh
for file in traces/*.cmd; do
    # test 14 and 16 are too slow with valgrind
    if [[ $file == *"14"* ]] || [[ $file == *"16"* ]]; then
        echo "Running test with $file without SIGALRM"
        valgrind -q --leak-check=full ./scripts/debug.py -d < "$file" > /dev/null
        continue
    fi
    echo "Running test with $file"
    valgrind -q --leak-check=full ./qtest < "$file" > /dev/null
done
```

除了 14 和 16 號之外的測試皆沒有問題，而分析 14 和 16 號測試發現錯誤是來自 `./qtest` 在超時後會直接報錯並中止程式，造成記憶體未正常釋放。（超時原因是因為 Valgrind 插入的測試程式嚴重脫慢程式）因此改為用 `./scripts/debug.py -d` 來取消 Alarm clock。
最後所有測試皆通過 Valgrind 測試，如下圖所示。
```sh
Running test with traces/trace-01-ops.cmd
Running test with traces/trace-02-ops.cmd
Running test with traces/trace-03-ops.cmd
Running test with traces/trace-04-ops.cmd
Running test with traces/trace-05-ops.cmd
Running test with traces/trace-06-ops.cmd
Running test with traces/trace-07-string.cmd
Running test with traces/trace-08-robust.cmd
Running test with traces/trace-09-robust.cmd
Running test with traces/trace-10-robust.cmd
Running test with traces/trace-11-malloc.cmd
Running test with traces/trace-12-malloc.cmd
Running test with traces/trace-13-malloc.cmd
Running test with traces/trace-14-perf.cmd without SIGALRM
Running test with traces/trace-15-perf.cmd
Running test with traces/trace-16-perf.cmd without SIGALRM
Running test with traces/trace-17-complexity.cmd
```

更新：後來才發現有 `make valgrind` 可以使用，仍然有通過測試。 

## Web 命令/網頁伺服器改善
> Commit [83ed391](https://github.com/BennyWang1007/lab0-c/commit/83ed391a6e117f0b2e6fcd9277f95e9d00e72fa2)

1. 首先我將變數 `web_connfd` 從區域變數變成全域變數，方便在 `report.c` 中進行是否有連線的判斷，以及關閉連線。
2. 接著在 `report()` ，在訊息尾部傳送`\0`給接收者，並關閉連線並重置變數 `web_connfd = 0;`。
```diff
void report(int level, char *fmt, ...)
{
    ...
    if (web_connfd) {
        ...
        web_send(web_connfd, buffer);
+       if (write(web_connfd, "\0", 1) == -1)
+           perror("write");
+       close(web_connfd);
+       web_connfd = 0;
     }
 }
```

3. 新增 `void send_response(int out_fd)` 取代原本的回覆： `HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n`，讓瀏覽器可以正常連接，解決favicon.ico的問題。
4. 新增測試程式 [test_web.c](https://github.com/BennyWang1007/lab0-c/blob/master/test_web.c) 測試結果如下，輸出有如預期。

```text
HTTP/1.1 200 OK
%s%s%s%s%s%sContent-Type: text/html

<html><head><style>body{font-family: monospace; font-size: 13px;}td {padding: 1.5px 6px;}</style><link rel="shortcut icon" href="#"></head><body><table>
l = []

7 7
Match
```
5. 使用 `curl` 命令測試結果
```shell
curl http://localhost:9999/new --output -
curl http://localhost:9999/ih/1 --output -
curl http://localhost:9999/ih/2 --output -
curl http://localhost:9999/ih/3 --output -
curl http://localhost:9999/sort --output -
curl http://localhost:9999/quit --output -
```
輸出也如預期：
```shell
<html><head><style>body{font-family: monospace; font-size: 13px;}td {padding: 1.5px 6px;}</style><link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"></head><body><table>
l = []
<html><head><style>body{font-family: monospace; font-size: 13px;}td {padding: 1.5px 6px;}</style><link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"></head><body><table>
l = [1]
<html><head><style>body{font-family: monospace; font-size: 13px;}td {padding: 1.5px 6px;}</style><link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"></head><body><table>
l = [2 1]
<html><head><style>body{font-family: monospace; font-size: 13px;}td {padding: 1.5px 6px;}</style><link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"></head><body><table>
l = [3 2 1]
<html><head><style>body{font-family: monospace; font-size: 13px;}td {padding: 1.5px 6px;}</style><link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"></head><body><table>
l = [1 2 3]
<html><head><style>body{font-family: monospace; font-size: 13px;}td {padding: 1.5px 6px;}</style><link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon"></head><body><table>
Freeing queue
```
6. 使用瀏覽器進行測試，如圖所示傳送了 `ih 2` 給伺服器，伺服器則返回了插入了 2 的佇列 `l = [2, 4, 5]`。
![web-server-chrome-test](https://hackmd.io/_uploads/rJaDXt6ikg.png)


> Commit [b6b6bf2](https://github.com/BennyWang1007/lab0-c/commit/b6b6bf247efd47e902fd4f9329d670d33f37536d)

在測試中發現使用瀏覽器傳送 request 的時候，會重複執行兩次命令，如下圖：
![web_double_requests](https://hackmd.io/_uploads/S1y9YuToJg.png)
終端也確實收到了兩份 request 並且兩個都接受了。終端顯示：
```shell
cmd> 
l = [2]
cmd> 
l = [2 2]
```

在與 [@nyraa](https://github.com/nyraa/) 詢問後，發現範例程式碼中 `"</style><link rel=\"shortcut icon\" href=\"#\">"` 會將 request 導到也就是`目前網址#`，但 `#` 會在瀏覽器內處理掉，因此等同於原網址。因此將 `href` 改為空的 data url ，以防止送出重複的 request，修改如下。

```diff
void send_response(int out_fd)
    ...
    "td {padding: 1.5px 6px;}"
-   "</style><link rel=\"shortcut icon\" href=\"#\">"
+   "</style><link rel=\"shortcut icon\" href=\"data:image/x-icon;,\" type=\"image/x-icon\">"
    "</head><body><table>\n";
    ...
```

修改後，當瀏覽器傳送空的 request 時，server端會收到 `.` 指令，如下圖：
![Unknown command '.'](https://hackmd.io/_uploads/rJ3Uod6iJe.png)
因此新增了command `.` 去處理。
```c
bool do_no_command(int argc, char *argv[])
{
    report(1, "Empty command");
    return true;
}

void init_cmd()
{
    ...
    add_cmd(".", do_no_command, "Empty command", "");
    ...
}
```

經過改動後，瀏覽器能進行正常的request，不會重複傳送之外，也解決了 `Unknown command '.'` 的問題。






