# 2024q1 Homework4 (quiz3+4)
contributed by < `w96086123` >
## [第三週測驗](https://hackmd.io/@sysprog/linux2024-quiz3)
### 測驗一
#### 公式解釋
為求 $N^2$ 的開根號,可利用 [Digit-by-digit calculation ](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation) 的方式取得。
可將 $N^2$ 拆分為由 n 個整數之和: $N^2 = (a_n + a_{n-1} + a_{n-2} + ... + a_0)^2,a_m=2^m\ or\ a_m=0$
將此展開之後可得:\begin{split} \\
N^2 =&\ a_n^2+[2a_n+a_{n-1}]a_{n-1}+[2(a_n+a_{n-1})+a_{n-2}]a_{n-2}+...+[2(\displaystyle\sum_{i=1}^{n}a_i)+a_0]a_0 \\
=&\ a_n^2+[2P_n+a_{n-1}]a_{n-1}+[2P_{n-1}+a_{n-2}]a_{n-2}+...+[2P_1+a_0]a_0 \\
P_m =&\ a_n+a_{n-1}+...+a_m \\
=&\ p_{m+1}+a_m\\
\end{split}
因此可知 $P_0$ 即為所求。
目的是從試著找出所有 $a_m$ ,因此由 n 試到 0 並且因為 $a_m$ 只有兩種可能 $a_m=2^m or\ 0$ ,因此求 $a_m$ 時只須試驗 $P_m^2 \leq N^2$ ,即可知道 $a_m$ 的值。
\begin{cases}
P_m = P_{m+1} + 2^m, & \text{if $P_m^2 \leq N^2$}\\
P_m = P_{m+1}, & \text{otherwise}
\end{cases}
由於每輪計算 $N^2 - P_m^2$ 的成本過高,因此改為利用上輪的差值 $X_{m+1}$ 減去 $Y_m$ 可得 $X_{m}$
$X_m = N^2 - P_m^2 = X_{m+1} - Y_m$
$Y_m = P_m^2 - P_{m+1}^2 = 2P_{m+1}a_m+a_m^2$
也就是紀錄上一輪的 $P_{m+1}$ 來調整。
\begin{split}
Y_m=&
\begin{cases}
c_m+d_m & \text{if } a_m=2^m \\
0 & \text{if } a_m=0
\end{cases}\\
c_m =& P_{m+1}2^{m+1} \\
d_m =& (2^m)^2 \\
\end{split}
拆分 ${Y_m}$ 可得上述的 $c_m$ 和 $d_m$ ,並且藉由位元運算推出下一輪
\begin{split}
c_{m-1}=&\ P_m2^m=(P_{m+1}+a_m)2^m=P_{m+1}2^m+a_m2^m=\begin{cases}
c_m/2+d_m & \text{if }a_m=2^m \\
c_m/2 & \text{if }a_m=0
\end{cases}\\
d_{m-1}=&\ (2^{m-1})^2 = (\dfrac{2^m}{2})^2 = \dfrac{(2^m)^2}{4}= \dfrac{d_m}{4}
\end{split}
由上述可知 `AAAA` 為 2 ,`BBBB` 為 1 。
#### 利用第二週測驗的 ffs 替代 `__builtin_clz`
```C
int i_sqrt(int x)
{
if (x <= 1) /* Assume x is always positive */
return x;
int z = 0;
for (int m = 1UL << ((31 - ffs(x)) & ~1UL); m; m >>= 1) {
int b = z + m;
z >>= 2;
if (x >= b)
x -= b, z += m;
}
return z;
}
```
#### 在 Linux 核心原始程式碼找出對整數進行平方根運算的程式碼,並解說其應用案例
此函數在 `\lib\math\int_sqrt.c`。
```c
unsigned long int_sqrt(unsigned long x)
{
unsigned long b, m, y = 0;
if (x <= 1)
return x;
m = 1UL << (__fls(x) & ~1UL);
while (m != 0) {
b = y + m;
y >>= 1;
if (x >= b) {
x -= b;
y += m;
}
m >>= 2;
}
return y;
}
```
找到 `int_sqrt` 被用在 `rwb_arm_timer` 中,而 `rwb_arm_timer` 的路徑是 `\block\blk-wbt.c` 。
```C
static void rwb_arm_timer(struct rq_wb *rwb)
{
struct rq_depth *rqd = &rwb->rq_depth;
if (rqd->scale_step > 0) {
/*
* We should speed this up, using some variant of a fast
* integer inverse square root calculation. Since we only do
* this for every window expiration, it's not a huge deal,
* though.
*/
rwb->cur_win_nsec = div_u64(rwb->win_nsec << 4,
int_sqrt((rqd->scale_step + 1) << 8));
} else {
/*
* For step < 0, we don't want to increase/decrease the
* window size.
*/
rwb->cur_win_nsec = rwb->win_nsec;
}
blk_stat_activate_nsecs(rwb->cb, rwb->cur_win_nsec);
}
```
此函數 `rwb` 代表的是 `request write back` ,因此可以知道它是一個寫入的函數。
此函數主要想要判斷是否有需要提昇寫入速度,若有需要則使用調整 window size 的方法進行提昇,且調整方法為透過 [Fast inverse square root](https://en.wikipedia.org/wiki/Fast_inverse_square_root) 。
但為何要使用 8 與 4 這樣狀況無法得知,只能猜測可能是基於實驗過後的結果所得出的結論。
### 測驗二
```C
#include <stdint.h>
void divmod_10(uint32_t in, uint32_t *div, uint32_t *mod)
{
uint32_t x = (in | 1) - (in >> 2); /* div = in/10 ==> div = 0.75*in/8 */
uint32_t q = (x >> 4) + x;
x = q;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
q = (q >> 8) + x;
*div = (q >> CCCC);
*mod = in - ((q & ~0x7) + (*div << DDDD));
}
```
主要想法為想要使用 bitwise 的方式進行除法運算,但 10 不是 2 的冪次,因此無法直接使用,所以此方法想把 in 改變成 $\frac{8}{10} in$ ,後使用右移三位取得 $\frac{1}{10}in$ 。
但 in 無法使用直接轉成 $\frac{8}{10}$ ,因此使用逼近法取得數值 `q = (q >> 8) + x` 。
根據上述的計算過程,可得知要取得商數只需要將 `q` 右移三位即可,因此 `CCCC` 為 3 。
則想要取得餘數可利用餘數定理取得,`((q & ~0x7) + (*div << DDDD))` 則為 $g*Q$ ,因此 $mod = in - g * Q$ 即為餘數。
### 測驗三
主要作法與 `__builtin_clz` 相似,尋找 MSB 的數值以取得以 2 為底的對數整數。
以長度為 32 位元的為例,若 `i` 大於 $2^{16}$ 代表前 16 位元中含有 bit 為 1 ,以此類推可得以下程式:
```c
static size_t ilog2(size_t i)
{
size_t result = 0;
while (i >= 65536) {
result += 16;
i >>= 16;
}
while (i >= 256) {
result += 8;
i >>= 8;
}
while (i >= 16) {
result += 4;
i >>= 4;
}
while (i >= 2) {
result += 1;
i >>= 1;
}
return result;
}
```
由於此方法可以直接使用 `__builtin_clz` 代替,因此也可得以下程式:
```c
int ilog32(uint32_t v)
{
return (31 - __builtin_clz(v));
}
```
### 測驗四
### 測驗五
```C
int ceil_ilog2(uint32_t x)
{
uint32_t r, shift;
x--;
r = (x > 0xFFFF) << 4;
x >>= r;
shift = (x > 0xFF) << 3;
x >>= shift;
r |= shift;
shift = (x > 0xF) << 2;
x >>= shift;
r |= shift;
shift = (x > 0x3) << 1;
x >>= shift;
return (r | shift | x > 1) + 1;
}
```
`shift` 用來記錄當下 msb 的值,並且使用 `r` 進行 `shift` 的累加,以取得 `ilog2` 的數值。在這過程中,需要注意到我們要取得的是上限值,因此在一開始進行 `-1` ,主要為了處理 `x` 剛好為 2 的指數次方狀況。如果沒有做此動作,在輸出時會因為回傳的 `+1` 而比正確答案多一。
#### 改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless
可以在進行運算前,先進行判斷是否 x 為 0 ,若是則改為 1 。後續處理方式一樣,程式碼如下。
```c
int ceil_ilog2(uint32_t x)
{
uint32_t r, shift;
x |= (x == 0);
x--;
r = (x > 0xFFFF) << 4;
x >>= r;
shift = (x > 0xFF) << 3;
x >>= shift;
r |= shift;
shift = (x > 0xF) << 2;
x >>= shift;
r |= shift;
shift = (x > 0x3) << 1;
x >>= shift;
return (r | shift | x > 1) + 1;
}
```