# 2024q1 Homework5 (assessment) contributed by < [tintinjian12999](https://github.com/tintinjian12999) > ## 從前 4 週的測驗題選出 3 題改進 ### quiz3 測驗一 > [題目](https://hackmd.io/@sysprog/linux2024-quiz3#%E6%B8%AC%E9%A9%97-1) #### 程式碼運作原理 [Digit-by-digit calculation](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation) 的作法是將數值視為基底的相加,假如想知道的平方根為 N,則可以將 N 寫為 $$N = \sum_{i = 0}^{n}a_i$$其中 $a_i$ 視基底而定,若為二進位則 $a_i = 2^i\ or\ 0$,目標是要找出每個位元是 $2^i$ 還是 $0$。 將 N 平方後可以得到 $$N^2 = (\sum_{i = 0}^{n}a_i)^2 = a_1^2 + 2a_n\sum_{i = 1}^{n - 1}a_i + a_n^2$$ 此時假設從 $2^n$ 開始已經有 $m$ 項被決定數值,則猜測解可以被寫為 $$P_m = a_n + a_{n-1}+...+a_m = \sum_{i = m}^{n}a_i $$ 而 $P_m$ 和 $a_m$ 的決定可以先假設 $$P_m = P_{m + 1} + 2^m$$ 若 $$P_m^2 \le N^2$$ 則 $a_m = 2^m$ 反之 $a_m = 0$ 且將 $P_m$ 設為 $P_{m + 1}$。 上述比較式中需要平方的部份可以透過儲存兩者間的差避免 令 $$X_m = N^2 - P_m^2$$此時 $$X_m = X_{m + 1} - Y_m$$ 其中$$Y_m = P_m^2 - P_{m + 1}^2 = 2P_{m + 1}a_m + a_m^2$$ 初始值設為$$a_n = P_n = 2^n$$ 用 $X_m < 0$ 作為判斷 $a_m$ 的基礎。 $Y_m$ 可以被拆解為 $c_m + d_m$,$c_m = 2P_{m + 1}a_m$,$d_m = (a_m)^2$,由此 $Y_m$ 可以被寫為 $$ Y_m = \left\{ \begin{array}{c} c_m + d_m, if\ a_m = 2^m \\ 0, if\ a_m = 0 \end{array} \right.$$ 在每次迴圈更新數值時 $$c_{m-1} = (P_{m + 1} + a_m)2^m = \left\{ \begin{array}{c} c_m/2 + d_m \ \ \ \ if\ a_m = 2^m\\ c_m/2 \ \ \ \ if\ a_m = 0 \end{array} \right.$$ $$d_{m-1} = \frac{d_m}{4}$$ 對應到程式碼中 $b = Y_m$,$m = d_m$,$z = c_m$,$x = X_{m+1}$ #### 使用 `ffs` 取代 `__builtin_clz(x)` 根據 linux manual 的敘述 >The ffs() function returns the position of the first (least significant) bit set in the word i. The least significant bit is position 1 and the most significant position is, for example, 32 or 64. ffs 會返回 least significant bit set的位置,如 ffs(2) 會返回 2,ffs(4) 會返回 3。 因此若要知道 most significant 的 1 在哪,可以透過以下程式 ```c int get_msb(int x) { int temp = x, msb = 0; while (temp) { int i = ffs(temp); msb += i; temp >>= i; } msb--; return msb; } ``` ```c int ffs_i_sqrt(int x) { if (x <= 1) /* Assume x is always positive */ return x; int z = 0; int msb = get_msb(x); for (int m = 1UL << (msb & ~1UL); m; m >>= 2) { int b = z + m; z >>= 1; if (x >= b) x -= b, z += m; } return z; } ``` #### 無分支的版本 參考[解讀計算機編碼](https://hackmd.io/@sysprog/binary-representation#%E4%B8%8D%E9%9C%80%E8%A6%81%E5%88%86%E6%94%AF%E7%9A%84%E8%A8%AD%E8%A8%88)可以改寫為以下程式碼 ```c for (int m = 1UL << (msb & ~1UL); m; m >>= 2) { int b = z + m; z >>= 1; z += (~(x - b) >> 31) & m; x -= (~(x - b) >> 31) & b; } ``` 使用 `perf` 可以得知 branch-misses 的情況進而比對無分支版本的差異 ```c int main() { for (int i = 0; i < 100000; i++) { int input = rand(); int result = ffs_i_sqrt(input); } return 0; } ``` 使用以上程式碼,用亂數產生隨機正整數重複 100000 次並使用 100 次測量的平均值 原始版本 ``` Performance counter stats for './test' (100 runs): 61,090,311 instructions # 0.72 insn per cycle ( +- 0.01% ) 84,543,881 cycles ( +- 0.24% ) 879,974 branch-misses # 7.38% of all branches ( +- 0.02% ) 11,917,337 branches ( +- 0.02% ) 0.026520 +- 0.000441 seconds time elapsed ( +- 1.66% ) ``` 無分之版本 ``` Performance counter stats for './test' (100 runs): 71,990,721 instructions # 1.01 insn per cycle ( +- 0.01% ) 71,186,457 cycles ( +- 0.14% ) 169,479 branch-misses # 1.62% of all branches ( +- 0.06% ) 10,496,528 branches ( +- 0.01% ) 0.022861 +- 0.000418 seconds time elapsed ( +- 1.83% ) ``` 可以看到 branch-misses 有明顯的提昇。 #### 在 Linux 核心原始程式碼找出對整數進行平方根運算的程式碼,並解說其應用案例,至少該包含 block 目錄的程式碼。 #### 閱讀〈Analysis of the lookup table size for square-rooting〉和〈Timing Square Root〉,嘗試撰寫更快的整數開平分根運算程式 ### quiz4測驗三 > [題目](https://hackmd.io/@sysprog/linux2024-quiz4) ## 閱讀教材:CSAPP > 不准看盜版 ### 第二章 #### C語言支援的資料型態 C 語言支援的基礎資料型態與其大小如下表 ![image](https://hackmd.io/_uploads/rytBpnLgA.png) 需要注意的是 `long` 根據機器的不同是有差異的。因此其能表達的數值範圍在 32 位元機器為 ![image](https://hackmd.io/_uploads/HJJ1R2Il0.png) 在 64 位元機器為 ![image](https://hackmd.io/_uploads/ByUe0nUl0.png) #### 整數表示式 ##### 無號整數 考慮一個由 $w$ 個位元組成的整數型態,將其寫成 $[x_{w-1}, x_{w_2}, ..., x_0]$,其中 $x_{wn}$ 都為 0 或 1,若要將以上的二進位數值寫成無號整數,可以透過以下表達式達成$$B2U_w = \sum_{i = 0} ^ {w - 1}x_i2^i$$ 如 $$B2U_4([1111]) = 15$$ 由此可已得知無號整數的最大值為 $$ UMax_w = 2^w - 1$$ 因此由 4 個位元組與 8 個位元組組成的無號整數數值範圍$$2 ^ {32} - 1 = 4,294,967,295$$ 和 $$2 ^ {64} - 1 = 18,446,744,073,709,551,615$$ ##### 有號整數 這裡使用的是[二補數](https://en.wikipedia.org/wiki/Two%27s_complement),其轉換函式為$$B2T_w = -x_{w-1} + \sum_{i = 0} ^ {w - 2}x_i2^i$$ 如$$B2T_4([1111]) = -1$$ $$B2T_4([0101])= 5$$ 相似地,有號整數的數值範圍為$$TMin_w = -2^w - 1$$和$$TMax_w = 2^{w-1} - 1$$ ##### 有號整數與無號整數的互換 可以簡單的看出兩者的換算為$$T2U_w(x) = \begin{cases} x + 2^w,\quad x<0\\ x,\quad x\ge0 \end{cases}$$ 另一方面$$U2T_w(u) = \begin{cases} u ,\quad u \le TMax_w\\ u - 2^w,\quad u\gt TMax_w \end{cases}$$ #### 浮點數表示式 ### 第十章 ### 第十二章 看起來並行多核心可以加速 for 迴圈的運算 ![image](https://hackmd.io/_uploads/r1oYesbXC.png) 不太清楚為什麼 > 解釋個別的做法,才能討論 ## 閱讀教材:Operating Systems: Three Easy Pieces ## 簡述想投入的專案 > 參照 [2023 年期末專題](https://hackmd.io/@sysprog/linux2023-projects),簡述你想投入的專案 (亦可建立新專案),至少選出 (或訂出) 二個。 ### 第六次作業:Integration 希望能夠投入[作業六](https://hackmd.io/@sysprog/linux2024-integration/%2F%40sysprog%2Flinux2024-integration-a)的開發。 ### Linux 亂數產生器研究 實驗室其他人有在做物理亂數產生相關的研究,正好可以以此做比較。 --- IEEE 754. Assume float is 32-bit ```c float float_mul10(float x) { // Using bitwise operations. No mul/div int exp = (int)x & (0x7f800000); exp <<= 3; (int)x |= exp & (0x7fffffff); x += 2; return x; } ``` // 10 = (2 << 3) + (2 << 0) // https://godbolt.org/ TODO: 繼續撰寫程式碼、測試,並附上分析 將浮點數轉型成整數並將 exp 的部分加 3 可以得到乘以 8 的結果,加 1 可以得到乘以 2 的結果,再將兩者相加即可完成乘以 10 。 ```c float float_mul10(float x) { // Using bitwise operations. No mul/div int i_x = *(int*) &x; int sign = i_x & 0x80000000; int exp = i_x & 0x7f800000; int frac = i_x & 0x007fffff; int exp1 = exp + (3 << 23); int exp2 = exp + (1 << 23); int result1 = sign | exp1 | frac; int result2 = sign | exp2 | frac; float result1_f = *(float*) &result1; float result2_f = *(float*) &result2; return result1_f + result2_f; } ``` 注意不能使用 `int i_x = (int) x`,因為這實際上會以整數的表示式來表達浮點數數值,而非將浮點數轉為整數,從下面範例可以觀察 ```c int main () { float x = 4.0; int i_x = (int) x; //Expected printf("%b \n", x); printf("%b \n", i_x); return 0; } ``` 輸出結果 ```c 10100010101110011011000001011000 100 ``` 兩者的二進制表示法明顯不一致。 TODO: ksort + extra