# linux2023 hw1: Cuda-Chen > [題目](https://hackmd.io/@sysprog/linux2023-summer-quiz0) > [GitHub](https://github.com/Cuda-Chen/linux-2023-summer/tree/main/hw1) ## 測驗 α ### 測驗 α − 1 AAAA 到 EEEE 部份,其位於 `st_remove()` 函式。該函式如下: ```c 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; AAAA; BBBB; return; } if (st_left(del)) { struct st_node *most = st_last(st_left(del)); if (del == *root) *root = most; CCCC; DDDD; return; } if (del == *root) { *root = 0; return; } /* empty node */ struct st_node *parent = st_parent(del); if (st_left(parent) == del) st_left(parent) = 0; else st_right(parent) = 0; EEEE; } ``` 根據函式註解,在 remove 一個 node 後,為了維護 S-tree 的 binary search tree 之性質,應該將即將被 remove 之 node 和其 left/right child 進行 replace(replace the node being remove with its left/right child)。最後,記得更新其 parent node。 所以: - `AAAA: st_replace_right(del, least)` - `BBBB: st_update(root, least)` - `CCCC: st_replace_left(del, most)` - `DDDD: st_update(root, most)` - `EEEE: st_update(root, parent)` FFFF 以及 GGGG 部份,其位於 `__treeint_dump()` 函式。該函式如下: ```c static void __treeint_dump(struct st_node *n, int depth) { if (!n) return; __treeint_dump(FFFF, depth + 1); struct treeint *v = treeint_entry(n); printf("%d\n", v->value); __treeint_dump(GGGG, depth + 1); } ``` 可以看出該函式要印出一個升冪排序,對於一個 binary search tree,印出升冪排序應該使用 inorder traversal 進行 traverse。 所以: - `FFFF: st_left(n)` - `GGGG: st_right(n)` ### 測驗 α − 2 待補 目前在 [ecf4580](https://github.com/Cuda-Chen/linux-2023-summer/commit/ecf4580b72499ffb006e6d7679bd90f510e48b21) 新增 rbtree 的測試,不過目前在 `#include <linux/rbtree.h>` 會遇到 `header not found error`(即便在 complie 給 `-I/usr/include` 也會遇到 error) test cases: - random insert/find/remove - sequential insert/find/remove ### 測驗 α − 3 待補 可以根據以下的方式建立一個 skewed binary search tree: - 給一個陣列:`[1, 2, 3, 4, 5, 6]` - 以 idx 順序輸入 - idx +2 -> -1 -> +2 -> -1 -> ... 的順去進行插入 ## 測驗 β - `HHHH: ((sz + mask) & ~mask)` ### 測驗 β − 1 - 給予一個變數 sz 之記憶體對齊位址。 - 當 alignment 為 power of two 時,使用 bit operation 來加速記憶體對齊位址之計算。 ### 測驗 β − 2 - https://elixir.bootlin.com/linux/latest/A/ident/align_up - 於 `v6.4.8` 的 kvm unit test 中可以看到使用 `align_up` 以計算記憶體對齊位址: ```c // https://elixir.bootlin.com/linux/v6.4.8/source/tools/testing/selftests/kvm/include/test_util.h#L158 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; } ``` ## 測驗 γ <s> > 題目:https://gist.github.com/jserv/38bca4ebfea1c434e1dfc15337f80eeb - `HHHH: pthread_cond_wait(&qs->cond_st, &qs->mtx_st)` - `JJJJ: pthread_cond_signal(&qs2->cond_st)` </s> ### 測驗 γ − 1 觀察 `HHHH` 以及 `JJJJ` 所位於之程式碼區段: ```c again: /* 用來確認 thread 是否可以開始執行 */ verify(pthread_mutex_lock(&qs->mtx_st)); while (qs->st == ts_idle) verify(HHHH); verify(pthread_mutex_unlock(&qs->mtx_st)); if (qs->st == ts_term) { return NULL; } assert(qs->st == ts_work); /* 執行 quick sort */ qsort_algo(qs); /* 用來從 thread pool 中挑選 thread 來執行 qsort */ verify(pthread_mutex_lock(&c->mtx_al)); qs->st = ts_idle; c->idlethreads++; if (c->idlethreads == c->nthreads) { for (i = 0; i < c->nthreads; i++) { qs2 = &c->pool[i]; if (qs2 == qs) continue; verify(pthread_mutex_lock(&qs2->mtx_st)); qs2->st = ts_term; verify(JJJJ); verify(pthread_mutex_unlock(&qs2->mtx_st)); } verify(pthread_mutex_unlock(&c->mtx_al)); return NULL; } verify(pthread_mutex_unlock(&c->mtx_al)); goto again; ``` ### 測驗 γ − 2 > 使用如下命令以啟用 Thread Sanitizer: > `gcc -Wall -o qsort-mt gamma_solution.c -lpthread -fsanitize=thread -fPIE -pie -g` 使用 Thread Sanitizer 後可以看到當 n 足夠大時會出現 data race: ```bash $ ./qsort_mt -n 1000 ================== WARNING: ThreadSanitizer: data race (pid=12354) Write of size 4 at 0x7b4000000080 by thread T1 (mutexes: write M0): #0 allocate_thread /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:214 (qsort_mt+0x3476) #1 qsort_algo /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:317 (qsort_mt+0x4193) #2 qsort_thread /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:354 (qsort_mt+0x45b1) Previous read of size 4 at 0x7b4000000080 by thread T2 (mutexes: write M2): #0 qsort_thread /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:346 (qsort_mt+0x44c7) Location is heap block of size 256 at 0x7b4000000000 allocated by main thread: #0 calloc ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:672 (libtsan.so.0+0x31edc) #1 qsort_mt /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:135 (qsort_mt+0x28fa) #2 main /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:506 (qsort_mt+0x500e) Mutex M0 (0x7fff4a605078) created at: #0 pthread_mutex_init ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1227 (libtsan.so.0+0x4bee1) #1 qsort_mt /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:133 (qsort_mt+0x28dd) #2 main /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:506 (qsort_mt+0x500e) Mutex M2 (0x7b40000000a8) created at: #0 pthread_mutex_init ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1227 (libtsan.so.0+0x4bee1) #1 qsort_mt /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:139 (qsort_mt+0x297f) #2 main /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:506 (qsort_mt+0x500e) Thread T1 (tid=12356, running) created by main thread at: #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8) #1 qsort_mt /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:147 (qsort_mt+0x2a8e) #2 main /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:506 (qsort_mt+0x500e) Thread T2 (tid=12357, running) created by main thread at: #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:969 (libtsan.so.0+0x605b8) #1 qsort_mt /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:147 (qsort_mt+0x2a8e) #2 main /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:506 (qsort_mt+0x500e) SUMMARY: ThreadSanitizer: data race /home/jio/c_code/linux-2023-summer/hw1/gamma_solution.c:214 in allocate_thread ================== ThreadSanitizer: reported 1 warnings ``` 可以看到如下的程式碼(註解所示)發生 data race: ```c 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; // <-- 這裡發生 data race verify(pthread_mutex_lock(&c->pool[i].mtx_st)); verify(pthread_mutex_unlock(&c->mtx_al)); return (&c->pool[i]); } verify(pthread_mutex_unlock(&c->mtx_al)); return (NULL); } ``` 在變更 thread 的 status 時,我們應該在 critical section 進行變更。因此程式碼應該修改如下: ```c 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; // <-- 這裡發生 data race 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); } ``` ### 測驗 γ − 3 [15471da](https://github.com/Cuda-Chen/linux-2023-summer/commit/15471da8452e1512d565adb654cc47da00713d6e) 先使用 `perf stat` 來看我們哪裡可以進行 optimization: ``` $ perf stat --repeat 5 ./qsort-mt Performance counter stats for './qsort-mt' (5 runs): 3,846.71 msec task-clock # 1.811 CPUs utilized ( +- 0.08% ) 67 context-switches # 17.403 /sec ( +- 2.24% ) 6 cpu-migrations # 1.558 /sec ( +- 15.81% ) 9,830 page-faults # 2.553 K/sec ( +- 0.01% ) 11,549,247,140 cycles # 3.000 GHz ( +- 0.06% ) 4,943,291,732 stalled-cycles-frontend # 42.79% frontend cycles idle ( +- 0.24% ) 13,990,699,557 instructions # 1.21 insn per cycle # 0.35 stalled cycles per insn ( +- 0.00% ) 1,909,353,615 branches # 495.939 M/sec ( +- 0.00% ) 108,735,936 branch-misses # 5.69% of all branches ( +- 0.03% ) 2.12391 +- 0.00267 seconds time elapsed ( +- 0.13% ) ``` 再來使用 `perf record -e stalled-cycles-frontend:u,instructions:u,cycles:u,branches:u` 來看 hotspots: ``` # stalled-cycles-frontend:u 52.31% qsort-mt qsort-mt [.] qsort_algo ▒ 26.38% qsort-mt qsort-mt [.] swapfunc ▒ 12.38% qsort-mt qsort-mt [.] num_compare ◆ 4.04% qsort-mt libc.so.6 [.] __random ▒ 1.82% qsort-mt qsort-mt [.] main ▒ 0.90% qsort-mt qsort-mt [.] med3 ``` 看到 `qsort_algo` 是個佔比最多的 hotspot,所以使用 annotate 來看看哪個 code 最常被用到: ```c │ while (pb <= pc && (r = CMP(thunk, pb, a)) <= 0) { ▒ 2.26 │478: mov -0xb0(%rbp),%rax ▒ 3.82 │ cmp -0xa8(%rbp),%rax ▒ │ ↓ ja 54b ▒ 3.31 │ mov -0x80(%rbp),%rdx ▒ 0.02 │ mov -0xb0(%rbp),%rax ▒ 0.05 │ mov -0x60(%rbp),%rcx ▒ 0.75 │ mov %rdx,%rsi ▒ 0.57 │ mov %rax,%rdi ▒ │ → call *%rcx ▒ 0.01 │ mov %eax,-0xcc(%rbp) ▒ 1.65 │ cmpl $0x0,-0xcc(%rbp) ▒ 10.77 │ ↑ jle 3ee ▒ │ } ``` 可以看到在 swap 部份是佔有比例最多的。 [b1ad756](https://github.com/Cuda-Chen/linux-2023-summer/commit/b1ad75610b1cb39c6040f85260db012795e907dd) 不過因為亂數生成是使用 `rand()`,考慮原本未排序資料過於固定,會影響實驗結果。參考[2021年作業-ksort](https://hackmd.io/@sysprog/linux2021-sort) 後,選擇 [Xoroshiro128+](https://en.wikipedia.org/wiki/Xoroshiro128%2B) 讓生成的亂數更亂,之後再跑`perf stat`: ``` $ perf stat --repeat 5 ./qsort-mt Performance counter stats for './qsort-mt' (5 runs): 3,914.86 msec task-clock # 1.857 CPUs utilized ( +- 0.11% ) 74 context-switches # 18.899 /sec ( +- 25.97% ) 19 cpu-migrations # 4.852 /sec ( +- 12.23% ) 9,836 page-faults # 2.512 K/sec ( +- 0.01% ) 11,737,718,399 cycles # 2.998 GHz ( +- 0.12% ) 5,255,276,215 stalled-cycles-frontend # 44.81% frontend cycles idle ( +- 0.32% ) 14,426,760,095 instructions # 1.23 insn per cycle # 0.36 stalled cycles per insn ( +- 0.00% ) 1,875,724,083 branches # 479.038 M/sec ( +- 0.00% ) 109,678,859 branch-misses # 5.85% of all branches ( +- 0.11% ) 2.10864 +- 0.00409 seconds time elapsed ( +- 0.19% ) ``` 我們也來測試在不同大小的 input 的執行時間: | N | time (measure in seconds) | | -------- | -------- | | 10^2 | ~0 | | 10^3 | 0.00252 | | 10^4 | ~0 | | 10^6 | 0.35 | | 10^7 | 3.86 | | 10^9 | 506 | 不免俗的,使用 `perf record -e stalled-cycles-frontend:u,instructions:u,cycles:u,branches:u` 來看 hotspots: ``` # stalled-cycles-frontend:u 54.81% qsort-mt qsort-mt [.] qsort_algo 26.06% qsort-mt qsort-mt [.] swapfunc 13.71% qsort-mt qsort-mt [.] num_compare 1.27% qsort-mt qsort-mt [.] main 0.98% qsort-mt qsort-mt [.] next 0.81% qsort-mt qsort-mt [.] med3 0.64% qsort-mt qsort-mt [.] rotl ``` 使用 annotate 來看看哪個是不是 swap 部份仍然最常被用到: ```c │ while (pb <= pc && (r = CMP(thunk, pb, a)) <= 0) { ▒ 1.90 │478: mov -0xb0(%rbp),%rax ▒ 2.75 │ cmp -0xa8(%rbp),%rax ▒ │ ↓ ja 54b ▒ 3.65 │ mov -0x80(%rbp),%rdx ▒ 0.03 │ mov -0xb0(%rbp),%rax ▒ 0.06 │ mov -0x60(%rbp),%rcx ▒ 0.49 │ mov %rdx,%rsi ▒ 0.68 │ mov %rax,%rdi ▒ 0.01 │ → call *%rcx ▒ 0.04 │ mov %eax,-0xcc(%rbp) ◆ 1.41 │ cmpl $0x0,-0xcc(%rbp) ▒ 9.80 │ ↑ jle 3ee ``` 目前看下來在 swap 其中的 `while (pb <= pc && (r = CMP(thunk, pb, a)) <= 0)` 最常被用到。我們可以從減少 swap 次數的思路出發進行最佳化。 (目前發現使用任何 sorting 配合使用 Xoroshiro128+ 會導致結果不正確(例如:-87 > 1000)) ~~在 [專題: lib/sort.c](https://hackmd.io/@sysprog/B146OVeHn) 中,我考慮使用 hybird sort 的 intro sort 進行效能改進的實做。~~ 我認為應該從 quick sort partition method 先進行 optimize(題目給的程式碼是使用 quick sort w/ Hoare partition),在以下兩篇文章提到可以使用 branchless Lomutor partition 來嘗試提昇效率: - [Hoare’s Rebuttal and Bubble Sort’s Comeback](https://blog.reverberate.org/2020/05/29/hoares-rebuttal-bubble-sorts-comeback.html) - 這篇提到的 optimized bubble sort 也可以嘗試 - [Lomuto’s Comeback](https://dlang.org/blog/2020/05/14/lomutos-comeback/) 或者也可以改成 lock-free thread pool。 [fcb4130](https://github.com/Cuda-Chen/linux-2023-summer/commit/fcb4130e850466feeedd3d2cb7ae0693a48c47e9) [e591bce](https://github.com/Cuda-Chen/linux-2023-summer/commit/e591bcee15efad63467406f4284a19232f3ecad9) 目前加入 optimized bubble sort,不過結果不正確,正在 debug。 [6ed1471](https://github.com/Cuda-Chen/linux-2023-summer/commit/6ed1471ab2489b3bef22acc40dc47eb90f8f26c6) 加入 slightly-optimized bubble sort 之後,在某些 input size 的執行時間有減少一些。 | N | time (measure in seconds) | | -------- | -------- | | 10^2 | 0.00209 | | 10^3 | 0.0018 | | 10^4 | 0 | | 10^6 | 0.353 | | 10^7 | 4.03 | | 10^9 | 495 | [2b61777](https://github.com/Cuda-Chen/linux-2023-summer/commit/2b61777b9037ec2cebafabdbff70f56cf5e1cd16) 使用 optimized bubble sort,不過執行時間在所有情境並沒有減少。 | N | time (measure in seconds) | | -------- | -------- | | 10^2 | ~0 | | 10^3 | 0.00213 | | 10^4 | 0.00509 | | 10^6 | 0.356 | | 10^7 | 4.07 | | 10^9 | 517 | ## MISC ### alpha - https://www.facebook.com/groups/system.software2023/permalink/709222697696554/ - https://hackmd.io/@sysprog/linux-rbtree - https://hackmd.io/Yai5h5nOTcyfeCU8GuDmng - https://github.com/Zimilo/kernel-dev-examples/blob/master/rbtree_example/example.c ### gamma - https://opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/kern/qsort.c - https://juejin.cn/s/c%E8%AF%AD%E8%A8%80qsort%E5%87%BD%E6%95%B0%E6%BA%90%E7%A0%81 - generic bubble sort - https://github.com/Bidzina/Generic-Bubble-Sort-in-C/blob/master/main.c