--- tags: quiz2, linux2023 --- # 2023q1 Homework2 (quiz2) contributed by < `EdwardCKC` > ## [測驗 `1`](https://hackmd.io/@sysprog/linux2023-quiz2#%E6%B8%AC%E9%A9%97-1) 題目: 針對給定無號 64 位元數值 `x` 找出最接近且大於等於 2 的冪的值,例如: next_pow2(6) = 8 next_pow2(15) = 16 next_pow2(64) = 64 `x |= x >> 1` 因為 `>>` precedence 比 `|` 高,所以執行順序是 `x = x | (x >> 1)` 因為每做一次 `x |= x >> 1` 會向 LSB 多擴展 1 個 1 `AAAA` 之前會有 8 個 1, 而 `AAAA` 之後需要 16 個 1, 所以 `AAAA = 8` 同理 `BBBB = 32` 最後使 `x` 中 MSB 的 1 擴展到 LSB 然後 `CCCC = x + 1` 剛好進位到下一個 2 的冪的值。 但如果 $x = 2^{n}$ || $x = 2^{64}$ 答案會 +1 或 integer overflow, 所以要 `x - 1` ### 配合 `__builtin_clzl` 改寫 ```cpp uint64_t next_pow2(uint64_t x) { /* If x is 0, the result is undefined. */ /* avoid x - 1 is 0 or -1 if x is 1 or 0 */ if (x <= 1) return 1; uint64_t carry = 64 - __builtin_clzl(x - 1); return 1UL << (carry); } ``` ### 在 Linux 核心原始程式碼找出類似的使用案例並解釋 從 [C 語言:數值系統的roundup_pow_of_two()](https://hackmd.io/@RinHizakura/HJ0rPhxyD#__rounddown_pow_of_two--__roundup_pow_of_two) 得知類似操作, [unsigned long __roundup_pow_of_two(unsigned long n)](https://docs.kernel.org/core-api/kernel-api.html?highlight=roundup_pow_of_two#c.rounddown_pow_of_two) Description *round the given value up to the nearest power of two - the result is undefined when n == 0 - this can be used to initialise global variables from constant data* 從`grep -r "rounddown_pow_of_two\|roundup_pow_of_two" linux-6.2.2/kernel/` 找到 `hashtab.c` 的 [`htab_map_alloc`](https://github.com/torvalds/linux/blob/master/kernel/bpf/hashtab.c#L456)有類似的使用案例 ```ccp htab->n_buckets = roundup_pow_of_two(htab->map.max_entries); ``` 在 [line 493](https://github.com/torvalds/linux/blob/master/kernel/bpf/hashtab.c#L493) 利用 `roundup_pow_of_two` 確保 `n_buckets` 的大小是2的冪的值 ### 編譯器能否產生對應的 x86 指令 在Compiler Explorer [x86-64 gcc 11.3](https://godbolt.org/z/TcooKce7W) 執行, 結果有 `bsrq` 指令 ## [測驗 `2`](https://hackmd.io/@sysprog/linux2023-quiz2#%E6%B8%AC%E9%A9%97-2) ```cpp 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; } ``` `len` 是 int 在記錄 bit 的有效長度,只有 `len` 的長度可以用 bitwise 操作 `!(i & i-1)` 判定 `i` 的bit長度是否需要 `len++` 最後的 `ans` 是先把 `ans` 向左移動(`ans << len`), 之後用 bitwise or 去把兩個值的 bit 接在一起再 mod M ### __builtin_{clz,ctz,ffs} 改寫 ```cpp for (int i = 1; i <= n; i++) ans = (i | (ans << (32 - __builtin_clz(i)))) % M; return ans; ``` 利用 `32 - __builtin_clz(i)` 算出位移量,可以減少決定 `len`的值的 if statement 和 `len` 參數 ## [測驗 `3`](https://hackmd.io/@sysprog/linux2023-quiz2#%E6%B8%AC%E9%A9%97-3) ## [測驗 `4`](https://hackmd.io/@sysprog/linux2023-quiz2#%E6%B8%AC%E9%A9%97-4)