--- tags: linux2023 --- # 2023q1 Homework3 (quiz3) contributed by < [chiangkd](https://github.com/chiangkd) > ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz CPU family: 6 Model: 158 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Stepping: 9 CPU max MHz: 3800.0000 CPU min MHz: 800.0000 BogoMIPS: 5599.85 ``` ## 測驗 1 **延伸問題**: 1. 指出 `treesort.c` 程式碼的缺失並改進 2. 利用 Linux 核心的 [list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h) 標頭檔改寫並整合進第一次作業的實驗程式碼中,探討 [Tree sort](https://en.wikipedia.org/wiki/Tree_sort) 的效率 3. 研讀 Linux 核心的 [Red–black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) 程式碼,比較給定的 `treesort.c` 實作手法,指出後者的改進空間並予以實作 ## 測驗 2 **延伸問題**: 1. 解釋上述程式碼運作原理,並比較用紅黑樹實作的 [priority queue](https://en.wikipedia.org/wiki/Priority_queue),應提供完整的測試程式和效能分析 2. Linux 核心也有 AVL tree 的身影,但陸續被紅黑樹替代,請揣摩 Linux 核心開發者的考慮因素,並搭配 git log 來解讀 >見 commit [b145425](https://github.com/torvalds/linux/commit/b145425f269a17ed344d737f746b844dfac60c82) 3. 研讀 Linux 核心原始程式碼,指出曾經/現在使用 AVL tree 的部分,對照 Linux 核心的 `rbtree.h`,探究是否可用後者替換,從而爭取到貢獻程式碼到 Linux 核心的機會 ## 測驗 3 **延伸問題**: 1. 解釋 [Linear feedback shift register (LFSR)](https://en.wikipedia.org/wiki/Linear-feedback_shift_register) 的運作原理,搭配在 [Linux 核心原始程式碼](https://github.com/torvalds/linux)中,用 git log 和 grep 找出 LFSR 的應用案例 2. 解釋上述程式碼運作的原理 3. 在 Linux 核心原始程式碼中找到 $log_2(x)$ 的實作 (整數),要涵蓋不同的整數型態寬度,以及是否能運用微處理器提供的指令來加速。也從 Linux 核心找出整數 $log_2(x)$ 的應用案例並解說。 4. `lab0-c` 提供類似的實作 [log2_lshift16.h](https://github.com/sysprog21/lab0-c/blob/master/log2_lshift16.h),解釋其原理並嘗試改進其效能 函式執行時設定 `num_of_buckets = 120`、`num_of_iterations` 設定大約為 100 萬,透過 `set_N_BUCKETS` 以及 `set_N_BITS()` ,設定全域變數 `N_BUCKETS = 120`, `N_BITS = log2_64(N_BUCKETS) = 6` ,接著將 `buckets` 分配空間並初始化,透過 `fill_buckets` 迭代 `iterations` 次並填滿 `buckets` 在 `fill_buckets` 中定義 `x = 0x98765421b16b00b5`、`lfsr_iter = 6 << 1 = 12`,每次迭代中 `tmp_bucket` 都會透過 `bucket_number(x)` 更新,且每一次迭代 `x` 值都會進行 `lfsr_iter = 12` 次 LFSR 操作 其中 `lfsr()` 函式實作 ```c static void lfsr(uint64_t *up) { uint64_t new_bit = ((*up) ^ ((*up) >> 3) ^ ((*up) >> 30) ^ ((*up) >> 55)) & 1u; /* don't have to map '+1' in polynomial */ *up = ((*up) >> 1) | (new_bit << 63); /* shift *up by 1 to RIGHT and insert new_bit at "empty" position */ } ``` ```graphviz digraph g{ node[shape=record, style=bold]; series[label="<1>1|...|<9>9|...|<34>34|...|...|<61>61|...|...|<64>64"] xor1[label=xor][shape=circle] xor2[label=xor][shape=circle] xor3[label=xor][shape=circle] {rank=same;xor3;xor2;xor1} series:64:s->xor3:e series:61:s->xor3:e series:34:s->xor2:e series:9:s->xor1:e xor3:w->xor2:e[constraint=none] xor2:w->xor1:e[constraint=none] xor1:w->series:1:w } ``` 可表示為多項式 $x^{64}+x^{61}+x^{34}+x^{9}+1$ 其中 `log2_64` 須計算以 2 為底的 x 的對數,且向下取整,程式一開始已藉由,巨集展開建立大小為 256 的表格的對數值,其中對數值 n 佔表格的 $2^n$ 格,以 `2, 3` 為例,預計輸出為 `1`,即查表中的第 2、第 3 格為 `1` 在程式內將輸入值 right shift 及 `if` 條件判斷其屬於哪個級距 ($2^1<x<2^8$、$2^8<x<2^{16}$...) 並查表作答,故 `AAAA` 至 `HHHH` 為 `56` 至 `0` ,等差為 `8` 的數值。 ```c unsigned int bucket_number(uint64_t x) { uint64_t mask111 = (1 << (N_BITS + 1)) - 1; uint64_t mask011 = (1 << (N_BITS)) - 1; /* one 1 less */ unsigned char leq = ((x & mask111) < N_BUCKETS); /* leq (less or equal) is 0 or 1. */ return (leq * (x & IIII)) + ((1 - leq) * ((x >> (N_BITS + 1)) & JJJJ)); /* 'x >> (N_BITS + 1)' : take different set of bits -> better uniformity. * '... & mask011' guarantees that the result is less or equal N_BUCKETS. */ } ``` `bucket_number` 函數將產生的 LFSR 數值得以均勻分佈,以原程式為例 - `mask111 = (1 << (N_BITS + 1)) - 1 = 127 (0b0111 1111)` - `mask011 = (1 << (N_BITS)) - 1 = 63 (0b0011 1111)` 接著如果 `leg` = 1,代表 `(x & mask111) < N_BUCKETS` 及他的值可以直接 return,如果 `leg` = 0,代表 `(x & mask111) >= N_BUCKETS` 即需要透過運算,否則 return value 會超過 `N_BUCKETS` 數值。 根據註解 - `IIII = mask111` - `JJJJ = mask011` ## 測驗 4 **延伸問題**: 1. 解釋程式碼運作原理 2. 改進程式碼,使其得以處理 `x = 0` 的狀況,並仍是 branchless 3. 在 Linux 核心原始程式碼找出 ceil 和 log2 的組合 (類似的形式),並解說應用案例 (提示: 在 Linux 核心排程器就有) ```c int ceil_log2(uint32_t x) { uint32_t r, shift; x--; r = (x > 0xFFFF) << 4; x >>= r; shift = (x > 0xFF) << 3; x >>= shift; r |= shift; shift = (x > 0xF) << 2; x >>= shift; r |= shift; shift = (x > 0x3) << 1; x >>= shift; return (KKKK) + 1; } ``` 題目要求輸出 $log_{2}^{x}$ 並向上取整 本質上是 binary search,假設輸入為 `33`,預期輸出為 `6` (因 $2^{6}=64>33$) 可表示為 ```shell 33 = 0b110011 輸出 6 = 110 r=0 shift=0 x= 0b0000 0000 0000 0000 0000 0000 0010 0001 x--; *start* --- x= 0b0000 0000 0000 0000 0000 0000 0010 0000 --- ↑ x<0xFFFF 1111 1111 1111 1111 --- r = 0 shift = 0 --- x= 0b0000 0000 0000 0000 0000 0000 0010 0000 --- ↑ x<0xFF 1111 1111 --- r = 0 shift = 0 --- x= 0b0000 0000 0000 0000 0000 0000 0010 0000 --- ↑ x>0xF 1111 --- r = 0 shift = 1 << 2 = 4 --- x >>= shift x = 0b0000 0000 0000 0000 0000 0000 0000 0010 = 4(decimal) --- ↑ r |= shift = 0b100 | x<0x3 11 --- x = 2 shift = 0 r = 4 --- need return (KKKK) + 1 KKKK = 5 = 0b101 ``` 以上述流程可知,在每次判斷是否大於判斷值 (`0xFFFF`, `0xFF`, `0xF`, `0x3`) 時,將是否大於儲存於 `r`,有進行偏移就會有 `shift` 值,每次 `x` 的偏移量儲存於 `shift`,且最後 `x` 會剩下最後兩個 bits `0bxx` 基本上最高位為 `1` 的 bit 就會決定這個數值夾在哪兩個 2 的冪次之間,所以這個過程旨在找到最高位為 `1` 的 bit 在哪一個位置,在第一個 `0xFFFF` 若需要偏移,代表輸入的 `x` 值大於 65537 (開頭有先 `x--`),即代表輸入的值大於 $2^{16}$,故 `r` 會先儲存 `0b10000 = 16` 代表,後續則以此類推。 最後以 `r | shift | x>>1` 統計準確在哪一個位置上,因題目要求 ceil,所以才會有開頭先 `x--` 最後算完再 +1