# 2021q1 Week1/2/3/4
:::warning
課堂問答簡記
請填入你的 GitHub 帳號名稱
:::
## Uduru0522
:question: `linux/list.h` 的 `list_swap()` 實做原理 ?
```cpp
/**
* list_swap - replace entry1 with entry2
* and re-add entry1 at entry2's position
* @entry1: the location to place entry2
* @entry2: the location to place entry1
*/
static inline void list_swap(struct list_head *entry1,
struct list_head *entry2)
{
struct list_head *pos = entry2->prev;
list_del(entry2);
list_replace(entry1, entry2);
if (pos == entry1)
pos = entry2;
list_add(entry1, pos);
}
```
:thinking_face:
:::spoiler {state="open"} **先試著把一些函式展開 :**
```c=
static inline void list_swap(struct list_head *entry1,
struct list_head *entry2)
{
struct list_head *pos = entry2->prev;
/* list_del(entry2); */{
/* __list_del_entry(entry2); */{
if (!__list_del_entry_valid(entry2))
return;
/* __list_del(entry2->prev, entry2->next); */{
entry2->next->prev = entry2->prev;
WRITE_ONCE(entry2->prev->next, entry2->next);
}
}
entry2->next = LIST_POISON1;
entry2->prev = LIST_POISON2;
}
/* list_replace(entry1, entry2); */{
entry2->next = entry1->next;
entry2->next->prev = entry2;
entry2->prev = entry1->prev;
entry2->prev->next = entry2;
}
if (pos == entry1)
pos = entry2;
/* list_add(entry1, pos); */
/* __list_add(entry1, pos, pos->next) */{
if (!__list_add_valid(entry1, pos, pos->next))
return;
pos->next->prev = entry1;
entry1->next = pos->next;
entry1->prev = pos;
WRITE_ONCE(pos->next, entry1);
}
}
```
:::
+ Line 11,12: 將 `entry2` 的前後接上,其中 `WRITE_ONCE()` 可以防止賦值的指令被拆分成多個小指令,或是在多執行緒被其他函式調用時時造成錯誤。
> `#define WRITE_ONCE(var, val) (*((volatile typeof(val) *)(&(var))) = (val))`
+ Line 15,16: 將 `entry2` 自 list 之中分離出來,經過 `__list_del_entry_valid()` 確認資料可用之後, 把當下 `entry2` 的 `prev` 以及 `next` 設為預設的 dead pointer (`LIST_POISON1` 以及 `LIST_POISON1`),當調用 `LIST_POISON` 時會導致 Page Fault。
+ Line 20-23: 調整指標至如下形式
```graphviz
digraph G{
rankdir = LR
splines=ortho
node[
style = filled;
shape = record;
color = darkolivegreen2;
] entry1 entry2
node[color = gray73, shape = box] e1_next e2_next e1_prev e2_prev
node[color = lightgoldenrod1, shape = cds] pos
node[style=invis, shape=record] ivs1 ivs2
{rank = same; e1_prev; e2_prev; ivs1;}
{rank = same; entry1; entry2;}
{rank = same; e1_next; e2_next; ivs2;}
e2_prev -> e2_next;
e2_prev -> e1_prev -> ivs1 [style=invis];
e2_next -> e1_next -> ivs2 [style=invis];
e1_prev -> entry1 -> e1_next;
e1_prev -> entry2 -> e1_next;
e1_next -> entry2 -> e1_prev [constraint=false];
pos -> e2_prev [shape=none];
}
```
+ Line 31-36: 利用 `list_add(new, prev, next)` 將新節點插入 `prev` 及 `next` 之間的行為,將 `entry1` 加入至原先 `entry2` 所在地點。
+ Line 26,27 用以對應傳入的兩個 node 是 `XXX` -> `entry1` -> `entry2` -> `YYY` 的情況。
---
## Nahemah1022
### 1. 在 [lab0](https://hackmd.io/B3s8kNxJSG6IdbE7EHBMSw) 作業中的 merge_sort 有什麼可以改進的地方
- merge sort 本身
- 程式中的註解太過簡短,可以針對 `if` 與 `else` 兩個區段分別註解會更加清晰
- 變數的命名也不夠直覺,`indirect` 的目的無法直接望文生義
- 其他做法
- 有嘗試過使用 quick sort 算法,但因為測試資料中含有 quick sort 的 worst case,造成效能下降無法通過 qtest
:::warning
TODO:弄清楚 qtest 如何檢驗效能
:::
### 2. 在 qtest 中 [Address Sanitize 的問題與解決方](https://hackmd.io/B3s8kNxJSG6IdbE7EHBMSw#%E4%BA%8C%E3%80%81%E4%BF%AE%E6%AD%A3-Address-Sanitize-%E4%B8%AD%E7%9A%84-qtest-%E9%8C%AF%E8%AA%A4)
- 存取到超出 `bool` 本身一個 byte 大小的空間造成錯誤
- 可以將所有 `bool` 都改成 `int`,但會需要連帶修改其他東西,不好維護
- 我的方法是直接分成兩個不同參數的 function,可以解決問題但可能會讓兩個 fucniton 的內容過於相似,應有更好的辦法
---
## RZHuangJeff
### 1. 在[quiz3](https://hackmd.io/@sysprog/linux2021-quiz3)中`xs_trim`如何比對欲移除的字元
* 問題
由於`xs_trim`支援讓呼叫者自訂欲剪除的字元,因此若欲排除的字元太多使用逐一比對的話將造成執行效能低落,因此引入另外的方法。
* 解決方法
透過查表的方式直接確認是否欲移除該字元。
在程式碼中,可以看到一個長度為32的`mask`陣列,`mask`陣列的初始值為0,並且可透過兩個macro: `set_bit`及`check_bit`進行建表及查詢。在通常的執行環境中,單一個位元所站的空間為1個byte,而程式碼中`mask`中元素的型態為`uint8_t`,因此可以將所有不同的字元對應到`mask`中的不同個bit,而這個工作由`set_bit`完成,具體的內容如下:
```cpp
#define set_bit(byte) (mask[(uint8_t) byte / 8] |= 1 << (uint8_t) byte % 8)
```
可以發現到`set_bit`將給定的`byte`分成上5位元及下3位元,分別成為存取`mask`的索引及其中的第幾個位元。以`byte = 'A'`為例: A的ascii碼為`0x41`,則存取`mask`的索引為8,並且其對應到的是`mask[8]`中的第2個字元(1 << 1 是2)。找到對應的位元後則透過or將該位元設成1。
表建好後,透過`check_bit`在常數時間內查詢該表,便可得知該字元是否為欲刪除之字元,`check_bit`的具體內容為:
```cpp
#define check_bit(byte) (mask[(uint8_t) byte / 8] & 1 << (uint8_t) byte % 8)
```
由上可知,只要透過與`set_bit`相同之方式找到`mask`中對應的位元,並且確認該位元是否為1。
### 2. 為何`xs_new`要分兩種方式儲存短字串及長字串
在`xs_new`中是將長度小於 16 的字串放在 stack,並為長度大於 16 的字串分配 heap 上的空間。這樣做的原因主要是效能考量,有以下兩點:
1. 分配 heap 空間較直接儲存於 stack 上更費時,因此為短字串分配 heap 空間的時間開銷較大
2. stack 的較 heap 接近先前已使用過的位址,如此可善用 cache 的 spatial locality
---
## hankluo6
https://hackmd.io/@hankluo6/2021q1quiz1
### 1. quiz2 的 get_middle 方式遇到的 worst case
上課時將 merge sort 想成 quick sort,merge sort 的 worst case 不應該為不平衡的切割,如果為不平衡切割的話,複雜度應為:
$$
T(n) = T(n - 1) + cn=T(1)+\frac{(n-1)}{n}-\frac{(n-1)(n-2)}{2}=O(n^2)
$$
這樣就與 quick sort 一樣為 $O(n^2)$,但 merge sort 的概念為 divide and conquer,worst case 應為 $O(nlogn)$,演算法需以 list 的中心點切割,使兩個 sub-list 皆盡量等長,再對 sub-list 做 merge sort,故 worst case 的複雜度與 best case 相同,應為:
$$
T(n) = T(\frac{n}{2})+T(\frac{n}{2})=O(nlogn)
$$
而會造成 merge sort 產生 worst case 的為 merge 時 compare 的次數,當兩個 sub-list 間的大小排序為互相交叉時會發生:
```graphviz
digraph G {
rankdir=LR;
node [shape=record];
a [label="{ <data> 1 | <ref> }"]
b [label="{ <data> 3 | <ref> }"];
c [label="{ <data> 5 | <ref> }"];
g [label="{ <data> 7 | <ref> }"];
a:ref:c -> b:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
b:ref:c -> c:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
c:ref:g -> g:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
d [label="{ <data> 2 | <ref> }"]
e [label="{ <data> 4 | <ref> }"];
f [label="{ <data> 6 | <ref> }"];
d:ref:f -> e:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false, arrowsize=1.2];
e:ref:f -> f:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
node [shape=plaintext]
"sublist B"->a [style=invis]
"sublist A"->d [style=invis]
}
```
此時 merge 將需要 m + n - 1 次的 comparison,根據 [Wikipedia](https://en.wikipedia.org/wiki/Merge_sort#cite_note-5),此數值最高為 $n ⌈lg n⌉ − 2⌈lg n⌉ + 1$。
### 2. Linux 核心的 `list_sort.c` 的切割方式
實現 botton-up 方式的 merge sort,以每兩個元素為單位切割,當合併後產生兩個 $2^k$ 數量的 list 時,便會合併成 $2^{k+1}$,並保證 $2^{k-1}, ..., 2^1$ 數量的 linked list 皆存在,這樣就能維持每次 merge (包含最後一次 `merge_final`)皆最高只會有 2:1 的數量比例,所以只要 $3 \times 2^{k}$ 數量的元素都可以容納在 cache 中,便能減少 cache miss。