---
# System prepended metadata

title: 2024q1 Homework5 (assessment)

---

# 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