> [題目](https://hackmd.io/@sysprog/linux2023-summer-quiz0)
## 測驗 α − 1
S-Tree 是不完整的 AVL tree。以 insertion 為例,AVL tree 有 LL, RR, RL, LR 四個不平衡 case; 其中 LL, RR 需要 1 個 rotate 做 rebalance; RL, LR 需要 2 rotate 做 rebalance。

> 林軒田教授公開的 [DSA 課程](https://www.csie.ntu.edu.tw/~htlin/course/dsa20spring/)筆記
S-Tree 只分左傾 (對應 AVL 的 LL, LR) 和右傾 (對應 AVL 的 LL, LR) 兩個不平衡 case, 都只用一個 rotate 處理 rebalance。所以 S-Tree 並不能處理 LR 和 RL 不平衡。
以 LR 不平衡為例, S-Tree 經過 st_rotate_left 後, 只會轉為 RL 不平衡。
```
st_rotate_left(<3>):
3 1
/ \
1 => 3
\ /
2 2
```
不過, S-Tree 還是有自平衡能力, 如再插入 2.5 為例, 產生新的 LR 不平衡, 經 st_rotate_left 後轉為 RL 不平衡; 然後再向上 update, 新的不平衡把之前的 RL 不平衡轉為 RR 不平衡, RR 不平衡可以由一個 rotate 轉為平衡。
```
st_rotate_left(<3>) st_rotate_right(<1>)
1 1 2
\ \ / \
3 => 2 => 1 3
/ \ /
2 3 2.5
\ /
2.5 2.5
```
S-Tree 的自平衡能力用一次操作不太能看得出來。
## 測驗 α − 2
在 remove 操作中, 當將要 remove 的 `del` 節點不是 leaf node 時, 需要進行 replace, 但是 `st_update` 不是從 leaf node 開始進行, 會使 st_right(least) 或 st_left(most) 子樹裡節點的 hint 不準確, 而影響後續操作的 rebalance。
在 `st_remove` 中, 先紀錄 leaf node 的親代節點, 由此親代節點開始進行 `st_update`。
```clike
void st_remove(struct st_node **root, struct st_node *del)
{
if (st_right(del)) {
struct st_node *least = st_first(st_right(del));
if (del == *root)
*root = least;
struct st_node *p_least = st_parent(least);
st_replace_right(del, least); //AAAA;
st_update(root, p_least == del ? st_right(least) : p_least); //BBBB;
return;
}
if (st_left(del)) {
struct st_node *most = st_last(st_left(del));
if (del == *root)
*root = most;
struct st_node *p_most = st_parent(most);
st_replace_left(del, most); //CCCC;
st_update(root, p_most == del ? st_left(most) : p_most); //DDDD;
return;
}
```
## 測驗 β − 1
當 `sz` 已經對齊時, `sz + mask` 會 set `sz` 後面的位元; `& ~mask` 會 clear 剛才被 set 的位元。
當 `sz` 不對齊時, `sz + mask` 會使不對齊的地址進位, `& ~mask` 會 clear 進位的位元後面位元使其對齊。
## 測驗 β − 2
在 bootlin 網站[搜尋 align_up](https://elixir.bootlin.com/linux/latest/A/ident/align_up), 找到其實作及用法:
實作 (kvm/include/test_util.h)
```c
/* Aligns x up to the next multiple of size. Size must be a power of 2. */
static inline uint64_t align_up(uint64_t x, uint64_t size)
{
uint64_t mask = size - 1;
TEST_ASSERT(size != 0 && !(size & (size - 1)),
"size not a power of 2: %lu", size);
return ((x + mask) & ~mask);
}
static inline uint64_t align_down(uint64_t x, uint64_t size)
{
uint64_t x_aligned_up = align_up(x, size);
if (x == x_aligned_up)
return x;
else
return x_aligned_up - size;
}
```
## 測驗 γ − 1
Quick sort 是一個 divide and conquer 的演算法, 可以采用 multithreading 方法做出平行運算的實作。
實作中有一個 thread pool:
```clike=107
struct qsort *pool; /* Fixed pool of threads.
```
idle 的 thread 在等待工作:
```clike=341
/* Wait for work to be allocated. */
verify(pthread_mutex_lock(&qs->mtx_st));
while (qs->st == ts_idle)
verify(pthread_cond_wait(&qs->cond_st, &qs->mtx_st));
verify(pthread_mutex_unlock(&qs->mtx_st));
```
Quick sort 的 first run 會 signal 第一個 thread 進行第一次 divide and conquer:
```clike=164
/* Hand out the first work batch. */
qs = &c.pool[0];
verify(pthread_mutex_lock(&qs->mtx_st));
qs->a = a;
qs->n = n;
qs->st = ts_work;
c.idlethreads--;
verify(pthread_cond_signal(&qs->cond_st));
verify(pthread_mutex_unlock(&qs->mtx_st));
```
Quick sort 每一個 run 會 divide 出兩個集合, 當兩個集合的元素同時足夠多時, 會找一個 idle thread 排序左集合 (line 313), 右集合由原來的 thread 排序 (line 324) :
```clike=312
/* Now try to launch subthreads. */
if (nl > c->forkelem && nr > c->forkelem &&
(qs2 = allocate_thread(c)) != NULL) {
qs2->a = a;
qs2->n = nl;
verify(pthread_cond_signal(&qs2->cond_st));
verify(pthread_mutex_unlock(&qs2->mtx_st));
} else if (nl > 0) {
qs->a = a;
qs->n = nl;
qsort_algo(qs);
}
if (nr > 0) {
a = pn - nr * es;
n = nr;
goto top;
}
```
當所有 thread 都 terminated 和 join 時, 即代表沒有需要排序的集合了, 完成排序。
## 測驗 γ − 2
加入編譯參數如下
```shell
gcc -Wall -o qsort qsort-mt.c -lpthread -fsanitize=thread -g
```
執行程式, 得得以下 WARNING:
```shall
==================
WARNING: ThreadSanitizer: data race (pid=899)
Write of size 4 at 0x7b4000000080 by thread T1 (mutexes: write M0):
#0 allocate_thread /home/kachun/linux2023/qsort-mt/qsort-mt.c:211 (qsort+0x3448)
#1 qsort_algo /home/kachun/linux2023/qsort-mt/qsort-mt.c:314 (qsort+0x415b)
#2 qsort_thread /home/kachun/linux2023/qsort-mt/qsort-mt.c:351 (qsort+0x4565)
#3 <null> <null> (libtsan.so.0+0x2d1af)
Previous read of size 4 at 0x7b4000000080 by thread T2 (mutexes: write M2):
#0 qsort_thread /home/kachun/linux2023/qsort-mt/qsort-mt.c:343 (qsort+0x4487)
#1 <null> <null> (libtsan.so.0+0x2d1af)
Location is heap block of size 256 at 0x7b4000000000 allocated by main thread:
#0 calloc <null> (libtsan.so.0+0x305ca)
#1 qsort_mt /home/kachun/linux2023/qsort-mt/qsort-mt.c:132 (qsort+0x28fb)
#2 main /home/kachun/linux2023/qsort-mt/qsort-mt.c:503 (qsort+0x4f7f)
Mutex M0 (0x7ffc43b47368) created at:
#0 pthread_mutex_init <null> (libtsan.so.0+0x4a636)
#1 qsort_mt /home/kachun/linux2023/qsort-mt/qsort-mt.c:130 (qsort+0x28de)
#2 main /home/kachun/linux2023/qsort-mt/qsort-mt.c:503 (qsort+0x4f7f)
Mutex M2 (0x7b40000000a8) created at:
#0 pthread_mutex_init <null> (libtsan.so.0+0x4a636)
#1 qsort_mt /home/kachun/linux2023/qsort-mt/qsort-mt.c:136 (qsort+0x2981)
#2 main /home/kachun/linux2023/qsort-mt/qsort-mt.c:503 (qsort+0x4f7f)
Thread T1 (tid=901, running) created by main thread at:
#0 pthread_create <null> (libtsan.so.0+0x5ea99)
#1 qsort_mt /home/kachun/linux2023/qsort-mt/qsort-mt.c:144 (qsort+0x2a86)
#2 main /home/kachun/linux2023/qsort-mt/qsort-mt.c:503 (qsort+0x4f7f)
Thread T2 (tid=902, running) created by main thread at:
#0 pthread_create <null> (libtsan.so.0+0x5ea99)
#1 qsort_mt /home/kachun/linux2023/qsort-mt/qsort-mt.c:144 (qsort+0x2a86)
#2 main /home/kachun/linux2023/qsort-mt/qsort-mt.c:503 (qsort+0x4f7f)
SUMMARY: ThreadSanitizer: data race /home/kachun/linux2023/qsort-mt/qsort-mt.c:211 in allocate_thread
==================
ThreadSanitizer: reported 1 warnings
```
由 ThreadSanitizer 檢測出來第 211 行程式碼產生 data race, 修正如下:
```c=205
static struct qsort *allocate_thread(struct common *c)
{
verify(pthread_mutex_lock(&c->mtx_al));
for (int i = 0; i < c->nthreads; i++)
if (c->pool[i].st == ts_idle) {
c->idlethreads--;
/* c->pool[i].st = ts_work; */
verify(pthread_mutex_lock(&c->pool[i].mtx_st));
c->pool[i].st = ts_work;
verify(pthread_mutex_unlock(&c->mtx_al));
return (&c->pool[i]);
}
verify(pthread_mutex_unlock(&c->mtx_al));
return (NULL);
}
```