--- tags: linux2023 --- # 2023q1 Homework2 (quiz2) contributed by < `Thegoatistasty` > > [測驗題目](https://hackmd.io/@sysprog/linux2023-quiz2) ## 測驗 `1` #### 1. 解釋上述程式碼原理,並用 `__builtin_clzl` 改寫 題目完整程式碼 ```c //original uint64_t next_pow2(uint64_t x) { x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 8; x |= x >> 16; x |= x >> 32; return x + 1; } //refined uint64_t next_pow2(uint64_t x) { x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; x |= x >> 32; return x + 1; } ``` 所謂**最接近且大於等於 2 的冪的值**,可以想成在2進位數值系統上,將最左邊的 1 往左移,並將右邊的 bits 全部設為 0。如: ``` 0001xxxxxx 轉為 0010000000 ``` 題目的方法是以數值最左邊的 1 為首,將右邊所有的 bits 設為 1,最後回傳該數值 + 1,簡化版過程如下: ``` 假設輸入數字為 00001xxxxxxxxxxx 程式執行時 ->000011xxxxxxxxxx ->00001111xxxxxxxx ->000011111111xxxx ->0000111111111111 最後 + 1 變成 ->0001000000000000 ``` **builtin_clzl 改寫** ```c uint64_t next_pow2(uint64_t x) { if (!x) return 1; int k = __builtin_clzl(x); return 1 << (64 - k); } ``` **2.在 Linux 核心原始程式碼找出類似的使用案例並解釋** ------------------待辦-------------------- **3. 當上述 clz 內建函式已運用時,編譯器能否產生對應的 x86 指令?** *有 bsrq* ``` .file "next_pow2.c" .text .p2align 4 .globl next_pow2 .type next_pow2, @function next_pow2: .LFB0: .cfi_startproc endbr64 movl $1, %eax testq %rdi, %rdi je .L1 bsrq %rdi, %rdi movl $64, %ecx xorq $63, %rdi subl %edi, %ecx sall %cl, %eax cltq ``` --- ## 測驗 `2` #### 1. 解釋上述程式碼運作原理 ```c int concatenatedBinary(int n) { const int M = 1e9 + 7; int len = 0; /* the bit length to be shifted */ /* use long here as it potentially could overflow for int */ long ans = 0; for (int i = 1; i <= n; i++) { // removing the rightmost set bit // e.g. 100100 -> 100000 // 000001 -> 000000 // 000000 -> 000000 // after removal, if it is 0, then it means it is power of 2 // as all power of 2 only contains 1 set bit // if it is power of 2, we increase the bit length if (!(i & (i-1))) len++; ans = (i | (ans << len)) % M; } return ans; } ``` **(1) mod($10^9+7$)** 參考資料:https://www.geeksforgeeks.org/modulo-1097-1000000007/ 簡單來說,這邊是用來防止overflow的 **(2)** ```if (!(i & (i-1)))``` 如同註解寫的,這裡用來判斷連接時要用幾個 bit ``` i = 1 去掉最左邊的 1 之後為 0,此時 len = len + 1 = 1,代表當前的數字要用 1 個 bits 表示 i= 2 (10) 去掉最左邊的 1 之後為 0,此時 len = len + 1 = 2,代表當前的數字要用 2 個 bits 表示 i = 3 (11) 去掉最左邊的 1 之後不為 0,len = 2 不變,代表當前的數字要用 2 個 bits 表示 . . . 以此類推 ``` #### 2. 嘗試使用 `__builtin_{clz,ctz,ffs}` 改寫,並改進 mod $10^9+7$ 的運算 ```c int concatenatedBinary(int n) { const int M = 1e9 + 7; /* use long here as it potentially could overflow for int */ long ans = 0; for (int i = 1; i <= n; i++) { ans = i | (ans << (32 - __builtin_clz(i))); ans = (ans > M)? ans : ans % M; } return ans; } ``` modulo改善策略:在有需要的時候做modulo就好 參考資料:[Built-in mod ('%') vs custom mod function: improve the performance of modulus operation](https://stackoverflow.com/questions/33333363/built-in-mod-vs-custom-mod-function-improve-the-performance-of-modulus-op) --- ## 測驗 `3` #### 1. 解釋上述程式碼運作原理,比較 SWAR 和原本的實作效能落差 ```c= size_t swar_count_utf8(const char *buf, size_t len) { const uint64_t *qword = (const uint64_t *) buf; const uint64_t *end = qword + len >> 3; size_t count = 0; for (; qword != end; qword++) { const uint64_t t0 = *qword; const uint64_t t1 = ~t0; const uint64_t t2 = t1 & 0x04040404040404040llu; const uint64_t t3 = t2 + t2; const uint64_t t4 = t0 & t3; count += __builtin_popcountll(t4); } count = (1 << AAAA) * (len / 8) - count; count += (len & BBBB) ? count_utf8((const char *) end, len & CCCC) : DDDD; return count; } ``` SWAR版和原本的差別是在它可以一次對比 8 bytes,最後再加上未滿 8 bytes的序列。 第 4 行做的事就是設定以 8 bytes 為單位的結尾,並在接下來的 for loop 計算 "後續位元組數量"。 字元數量則 = 全長 - "後續位元組數量",得出 *AAAA = 3* 又因為以 8 bytes 為單位,會有餘數問題,所以在 17 行再加上剩下 bytes 的位元數 所以 *BBBB = 7, CCCC = 7, DDDD = 0* #### 2. 在 Linux 核心原始程式碼找出 UTF-8 和 Unicode 相關字串處理的程式碼,探討其原理,並指出可能的改進空間 ----------待辦---------- --- ## 測驗 `4` #### 1. 解釋上述程式碼運作原理 將符合 pattern 的整數轉為 2 進制後就可以很容易看出規律:符合 pattern 的整數存在一個位置,使得左邊的位元都為 1,右邊都為 0 ``` pattern: 8000 (32768) = 1000 0000 0000 0000 pattern: c000 (49152) = 1100 0000 0000 0000 pattern: e000 (57344) = 1110 0000 0000 0000 pattern: f000 (61440) = 1111 0000 0000 0000 pattern: f800 (63488) = 1111 1000 0000 0000 (以下略) ``` 而精簡版程式碼可以寫為 ```c bool is_pattern(uint16_t x) { const uint16_t n = -x; return (n ^ x) < x; } ``` 原理大概是這樣 (參考[chun61205](https://hackmd.io/@roger61205/quiz2-2023#%E6%B8%AC%E9%A9%97-4)) ``` 將所有符合的數字列出 1000000000000 1100000000000 1110000000000 . . . 1111111111111 ``` 二補數後會將他們變成 ``` 1000000000000 0100000000000 0010000000000 . . . 0000000000001 ``` 再跟原本做 XOR ``` 0000000000000 1000000000000 1100000000000 . . . 1111111111110 ``` 相當於是 clear 最右邊的那個 1 所以結果必小於原本的數字