# 2024 Homework4 (quiz3+4)
contributed by <HotMercury>
## 第 3 週測驗題
### isqrt1: bitwise 操作
在執行第一版 `i_sqrt` 時嘗試使用 `gcc` 編譯,有以下錯誤,但如果把 n 變成常數,就可以正常執行,[error In using math function in gcc?](https://stackoverflow.com/questions/15737932/error-in-using-math-function-in-gcc) 可以知道如果使用的是 const 編譯器會最佳化成對應的值,如果要正確執行要加上 `-lm` link math library.
```diff
- int msb = (int)log2(n);
+ int msb = (int)log2(16);
```
```
$ gcc sqr_t.c
/usr/bin/ld: /tmp/ccmYLpqw.o: in function i_sqrt:
sqr_t.c:(.text+0x23): undefined reference to log2
```
[第一版的方式](https://hackmd.io/@sysprog/linux2024-quiz3#%E6%B8%AC%E9%A9%97-1) 可以得知 $\log{n}$ 與 $\sqrt{n}$ 間的大小不會影響答案。
> 這裡做更正我原本以為的作法是找到最靠近且略小於 $\sqrt N$ 的方式再繼續找,但現在發現是找到 N 向上取 $log_2 -1$ 這樣可以確保最高位,之後再以 2 的冪逼近。
$$
\log_2{n} < \sqrt{n}, n >= 16
$$

接下來思考如何不透過 `log` 也可以保有原有的精度,isqrt2 就是透過 shift 方式判斷 MSB 在第幾位。
#### 直式開方
上述 isqrt1 和 isqrt2 都是逐位元搜尋,且乘法成本太高,接下來可以使用 [Digit-by-digit calculation](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Digit-by-digit_calculation) 直式開方的方法。
跟著[公式](https://hackmd.io/@sysprog/linux2024-quiz3#%E6%B8%AC%E9%A9%97-1)實際推演
$P_0$ 及為我們想要求的平方根,所以我們從最高位慢慢遞減 $P_m$。
$$
P_0 = a_n + a_{n-1} + ... + a_0
$$
但不想要透過成本過高的乘法,因此使用上一輪差值 $X_{m-1}$ 減去($P_m^2 - P_{m+1}^2$) : $Y_m$
$Y_m$ 又由以下組成
- $c_{m-1} = \begin{cases} c_m/2 + d_m & if \ a_m = 2^m \\ c_m/2 & if \ a_m = 0 \end{cases}$
- $d_{m-1}= d_m/4$
程式邏輯
- $X_{n+1} = N$
- $c_n = 0$ 一開始還沒有值
- $d_n = ({2^m})^2$
先透過 `__builtin_clz` 找到 msb, 在依據上述數學式完成,以下為對應代號程式碼
```c
int i_sqrt3(int n)
{
if (n <= 1) /* Assume n is always positive */
return n;
int c = 0;
for (int d = 1UL << ((31 - __builtin_clz(n)) & ~1UL); d; d >>= 2) {
int y = c + d;
c >>= 1;
if (n >= y)
n -= y, c += d;
}
return c;
}
```
#### 改寫第三版程式
1. 直接透過 C library 的方式呼叫 `ffs`
[commit 575c64b](https://github.com/HotMercury/2024_linux_quize/commit/575c64bd18918b7263775221282c5882511ea94a)
2. 使用[第二周測驗](https://hackmd.io/@sysprog/linux2024-quiz2#%E6%B8%AC%E9%A9%97-3)用到的 `__ffs` 函式可以應用在 32 位元的參數
這裡先只用 32 位元,因此先不啟用 #define BITS_PER_LONG 64,這裡用的方式是 binary search 的變形,透過 bitwise 的操作可以減少次數,以 `i_sqrt1` 為例子,需要一個一個右移,而 binary 只需要以 2 的冪來尋找。
[commit 2fffd11](https://github.com/HotMercury/2024_linux_quize/commit/2fffd11fe8dc6d897a86ed66c012efa6d289f8b7)
3. 不使用到 branch,使用 bitwise 的方式
a. 先將第一個為 1 的位置以後都設成 1
b. 透過 bitwise 算出總共有多少個 1 bit
c. total bit - 算出的 bit 數
#### [block](https://github.com/torvalds/linux/tree/master/block) 應用到程式碼
我在 [`block/blk-wbt.c`](https://elixir.bootlin.com/linux/latest/source/block/blk-wbt.c#L409) 中找到 `rwb_arm_timer` 會使用到 `int_sqrt`,接下來要嘗試理解這個 function 在做什麼。為了測試我能不能有觀察 linux kernel 的 sense,我先單純看 function 來猜測,以 block 底下來說 rwb 可能就是 request write back,接下來 arm 跟 timer 就是 arm 架構下 write back 的時間,以上猜測時間結束。
**trace code**
第一步嘗試去找誰呼叫了這個 function,`wb_timer_fn` and `wbt_wait`但可惜的是資料結構太多且不熟悉用途,所以從 `rwb_arm_timer` 程式碼下手,先拿到 `struct rq_depth` 可以從裡面得知會有兩種情況來調整 window size(這裡還不知道是什麼,目前猜是時間)
- 當 scale_step > 0 :會算出新的 window size
- 否則照舊
在計算新的 window size 裡註解有提到 `We should speed this up` 所以目前是認為這個是特定方式的運算,之後提到 [fast integer inverse square](https://en.wikipedia.org/wiki/Fast_inverse_square_root) 是一個計算 $\frac{1}{\sqrt{x}}$ 的方法,
```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);
}
```
看到一個很有趣的 patch [blk-wbt: fix a divide-by-zero error in rwb_arm_timer()](https://lkml.indiana.edu/hypermail/linux/kernel/2104.2/01733.html) 之後嘗試理解
### Testing 2 : 減少 mod 10 和 div 10 操作
針對字串加法,暴力法可以通過 carry bit 的方式往前累進,所以一次運算會用到一次除法(for carry) 與 mod 運算,接下來參考《[Hacker's Delight](http://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf)》我們想用 bitwise 的方式取代 divide and mod operation。
想要透過 bitwise 計算的方式一定是 $\frac{an}{2^N}$ (因為分母要用 shift 的方式),計算出符合 $1.9 \leq \frac{19}{x} \leq 1.99 \Longrightarrow 9.55 \leq x \leq 10$ 目前 a : 13, $2^N$ : 18,只要在範圍內就可以,( 這裡教材寫只要判定最大的不會超過就好,這個還需要理解 )。
再來我們的目標就是要乘 13 除 128,這裡我一開始想到的是直接使用乘法,但這裡將 13 拆分為 2 進位的方式,以此達到乘 13 的效果,底下為 $\frac{13tmp}{8}$
```c
(tmp >> 3) + (tmp >> 1) + tmp) << 3)
```
而以下說明一個問題,即右移後,會有部分位元被捨棄,因此要另行處理。並不是很清楚為什麼是拿 q 來右移且要加三個,所以我嘗試用定點數的方式來計算。
```diff!
- ((((tmp >> 3) + (tmp >> 1) + tmp) << 3) + d0 + d1 + d2)
// 使用 Q3 的方式
+ q = n + (n << 2) + (n << 3);
+ q = q + (1 << (3 - 1));
```
如何解出以下的 code
- $x = \frac{4}{5} \times in$
- $q = \frac{17}{16} \times x$
其他的還沒想出來
```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));
}
```
#### 解析 CPU 週期數量
## 第 4 週測驗題
### Testing 3
#### rbtree
`udpate` 不要太頻繁