--- tags: linux2022 --- # 2022q1 Homework2 (quiz2) contributed by < `eric88525` > > [第 2 週測驗題](https://hackmd.io/@sysprog/linux2022-quiz2) # Q1 ## Q1-1 > 解釋上述程式碼運作的原理 ==EXP1== = `a & b & 1` ==EXP2== = `a & b` ==EXP3== = `a ^ b` + a 和 b 最低位元都是 0 的時候運算結果會正確 + 最低位元為 1 時 shift right 會遺失相關資訊,因此要補上最低位元的數值 + 取得最低位元方法為`&1`,唯有當 a 和 b 最低位元都是 1 時才需要+1,因此 exp1 = a & b & 1 ```c uint32_t average(uint32_t a, uint32_t b) { return (a >> 1) + (b >> 1) + (a & b & 1); } ``` ``` 推導: a + b = a ^ b + a & b << 1 (a+b)/2 = (a ^ b + a & b << 1) >> 1 = a ^ b >> 1 + a & b ``` 最終程式如下 ```c uint32_t average(uint32_t a, uint32_t b) { return (a & b) + ((a ^ b) >> 1); } ``` # Q1-2 > 比較上述實作在編譯器最佳化開啟的狀況,對應的組合語言輸出,並嘗試解讀 + on X86-64 linux : + `eax` is the return value register + `edi` is the first function argument + `esi` is the second function argument > 第一種版本,編譯器選項`-O1` ```c average(unsigned int, unsigned int): // move argument a to eax register and shift right mov eax, edi shr eax // move argument b to eax register and shift right mov edx, esi shr edx // do (a>>1) + (b>>1) add eax, edx // do a & b & 1 and edi, esi and edi, 1 // add result with (a>>1) + (b>>1) add eax, edi ret ``` > 第二種版本,編譯器選項`-O1` ```c average(unsigned int, unsigned int): // move argument a to eax register mov eax, edi // xor with b xor eax, esi // eax(a) shift right shr eax // a&b and edi, esi // (a & b) + ((a ^ b) >> 1) add eax, edi ret ``` 第二種方式指令數量變少,幾乎是都縮減一半 | 指令執行次數 | mov | shr | add | and | xor| | -------- | -------- | -------- | -------- | -|-| | 方法1 | 2 | 2 | 2 |2 |0| | 方法2 | 1 | 1 | 1 |1|1| + 參考資料: + [call function in assembly](https://www.cs.uaf.edu/2015/fall/cs301/lecture/09_14_call.html) + [the stack, registers and assembly code](https://blog.holbertonschool.com/hack-virtual-memory-stack-registers-assembly-code/) + [what is dword and ptr](https://www.itread01.com/content/1509039496.html) # Q2 ## Q2-1 > 解釋上述程式碼運作的原理 ==EXP4== = `a^b` ==EXP5== = `a<b` + 如要取 max 要讓 + a>b 的情況 ((EXP4) & -(EXP5)) = 0 + a<b 的情況 ((EXP4) & -(EXP5)) = a ^ b + 因此 exp4 在此直接設定為 `a^b`,並交由 `-(EXP5)` 來決定該為0或是`a^b` + -(EXP5) 應該要是全1或全0,才能決定 exp4 為0或維持原樣 + exp5 如果是0(false),取負號依舊是0,在 a>b 情況下需要 + exp5 如果是1(true),取負號變成-1,在 uint32_t 下為 `0xFFFFFFFF`,在 a<b 情況需要 + 根據前二點,要讓 a>b 情況下 exp5 為 0,可回推 exp5 為 `a<b` ```c #include <stdint.h> uint32_t max(uint32_t a, uint32_t b) { return a ^ ((a^b) & -(a<b) ); } ``` ## Q 2-2 > 針對 32 位元無號/有號整數,撰寫同樣 branchless 的實作 + 概念源自這篇 [文章](https://www.geeksforgeeks.org/compute-the-minimum-or-maximum-max-of-two-integers-without-branching/) + 利用 `((x - y) & -(x < y))` 來讓 x-y 是否維維持或設為 0,因為 `-(x>y)` 有二種結果 + -1 = 0xffffffff + -0 = 0x00000000 ```c uint32_t mymax(uint32_t x, uint32_t y) { return x - ((x - y) & ((x - y) & -(x < y))); } ``` # Q3 ## Q3-1 > 解釋上述程式運作原理 ==COND== = `v` ==RET== = `u << shift` [輾轉相除法原理](https://www.youtube.com/watch?v=fGesPF3QA1U&ab_channel=Stepp%E5%AD%B8%E9%99%A2),概念如下 1. 設要找出`a`和`b`的最大工因數, 如果`a`比`b`大,`a`可寫成如下,`q`是除數`r`是餘數 $a = b*q + r$ 2. 問題能轉成 $gcd(a,b) = gcd(b,r)$ 3. 直到`b`或`r`其中一方為0就為解答 ```c uint64_t gcd64(uint64_t u, uint64_t v) { // u 和 v 都是 0 所以回傳 0 if (!u || !v) return u | v; int shift; // 找出 u 和 v 最大能有多少 2^shift 的公因數 for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } // u 和 v 不再有2是公因數,因此把2移除 while (!(u & 1)) u /= 2; do { // u 和 v 不再有2是公因數,因此把2移除 while (!(v & 1)) v /= 2; // 輾轉相除法 if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (v); // 還原最前面有多少 2^shift 公因數 return u << shift; } ``` ## Q3-2 ```c uint64_t gcd64(uint64_t u, uint64_t v) { // u 和 v 都是 0 所以回傳 0 if (!u || !v) return u | v; int shift; // 找出 u 和 v 最大能有多少 2^shift 的公因數 for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } // u 和 v 不再有2是公因數,因此把2移除 while (!(u & 1)) u /= 2; do { // u 和 v 不再有2是公因數,因此把2移除 while (!(v & 1)) v /= 2; // 輾轉相除法 if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (v); // 還原最前面有多少 2^shift 公因數 return u << shift; } ``` # Q4 ## Q4-1 > 解釋上述程式運作原理,並舉出這樣的程式碼用在哪些真實案例中; ==EXP6== = `bitset & -bitset` + uint64_t 採用 2 補數,如果讓某數與其2補做 and 運算,可以保留最低1位元 ``` 0101(4) 0010(2) & 1011(-4) 1110(-2) ----------------------- 0001 0010 ``` ```c #include <stddef.h> size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; uint64_t bitset; for (size_t k = 0; k < bitmapsize; ++k) { bitset = bitmap[k]; while (bitset != 0) { uint64_t t = EXP6; int r = __builtin_ctzll(bitset); out[pos++] = k * 64 + r; bitset ^= t; } } return pos; } ``` # Q5 ## Q5-1 # Q6 ## Q6-1 ==M== = `_h` ==X== = `0` 1. 先創立 struct { char c; t _h; } *的指標,初始化起點為 0 2. 利用 -> 指定其 t 型態的開始位址 3. 減去 0 就是其 alignment 圖例: 以 ALIGNOF(int) 示範 ![](https://i.imgur.com/Eb24SKj.png) ```c /* * ALIGNOF - get the alignment of a type * @t: the type to test * * This returns a safe alignment for the given type. */ #define ALIGNOF(t) \ ((char *)(&((struct { char c; t _h; } *)0)->_h) - (char *)0) ``` 為了驗證,用 gdb 來觀察記憶體位置 ```c struct my_type{ char c_type; int int_type; }; int main(){ struct my_type *p = 0; return 0; } ``` gdb 執行結果如下 ``` (gdb) print p $5 = (struct my_type *) 0x0 (gdb) print &p->int_type $4 = (int *) 0x4 ``` 可以得知alignment為 0x4 - 0x0 = 0x4 # Q7 ## Q7-1 ==KK1== = `div3` ==KK2== = `div5` ==KK3== = `div3<<2` + div3 和 div5 需輸出長度為4的字串,因此 KKK1 和 KKK2 應該填入 `div3` `div5` 來透過 shift 增加 strncpy 的複製長度 `length` + 2是基本值,因要在不被3或5整除的情況下輸出原有數字 `%u` + 被3或5其中一個整除 shift 一次,都被整除 shift 二次 | div3 | div5 | length | | -------- | -------- | -------- | | 0 | 0 | 2 | | 0 | 1 | 4 | | 1 | 0 | 4 | | 1 | 1 | 8 | + 在只有被5整除情況下,應該從第4個字(B)開始輸出 ``` 9 = 1001b 1001b >>1 = 100b = 4 ``` + 在只有被3整除情況下,應該從第0個字(F)開始輸出 ``` 9 = 1001b 1001b >> (1<<2) = 0 ``` + 被3和5整除下,會如同上面從第0個字(F)開始輸出 ```c static inline bool is_divisible(uint32_t n, uint64_t M) { return n * M <= M - 1; } static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1; static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1; int main(int argc, char **argv) { for (size_t i = 1; i <= 100; i++) { // 檢查是否能被 3 5 整除 uint8_t div3 = is_divisible(i, M3); uint8_t div5 = is_divisible(i, M5); // 設定要複製幾個字串 unsigned int length = (2 << div3) << div5; char fmt[9]; // 拷貝所需字串 透過 shift 決定起點 strncpy(fmt, &"FizzBuzz%u"[(9 >> div5) >> (div3<<2)], length); fmt[length] = '\0'; printf(fmt, i); printf("\n"); } return 0; } ```