# 2024q1 Homework4 (quiz3+4)
> contributed by <`brian049`>
## [Quiz 3](https://hackmd.io/@sysprog/linux2024-quiz3)
### 測驗三
在測驗三當中針對 ilog 有三種版本,第一種版本透過不斷向右位移二進位位元來計算以 2 為底的對數。
```c
int ilog2(int i)
{
int log = -1;
while (i) {
i >>= 1;
log++;
}
return log;
}
```
第二種版本與第一種版本相似,但是是透過比較較大數字開始,一步一步將二進位位元向右位移到 0。
```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;
}
```
相對於第一種方式,第二種方式較為快速,我透過 Linux shell command `time` 指令來計算執行時間。
針對兩種方式我透過用兩種測資,一個測資為 65536,另一個測資為 87654321。
```shell
$ time ./v1
The logarithm base 2 of 65536 is: 16
real 0m0.006s
user 0m0.002s
sys 0m0.004s
$ time ./v2
ilog2 of 65536 is 16
real 0m0.008s
user 0m0.001s
sys 0m0.007s
```
```shell
$ time ./v1
The logarithm base 2 of 87654321 is: 26
real 0m0.027s
user 0m0.009s
sys 0m0.018s
$ time ./v2
ilog2 of 87654321 is 26
real 0m0.007s
user 0m0.001s
sys 0m0.006s
```
經過測試發現,若是測資較小,執行時間較無差異,但是若是測資較大,則執行時間差異較大。
第三種方式是透過呼叫 `__builtin_clz` 巨集來計算在二進位當中最高位元為 1 的位置,來得到以 2 為底的對數。
```c
int ilog32(uint32_t v)
{
return (31 - __builtin_clz(v | 1));
}
```
### 測驗三延伸問題
在 [linux/log2.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/log2.h) 當中有 ilog2 巨集的定義。
其實作的方法是暴力法(Brute force) 從 63 位元開始比對,若是向左位移 63 位元得到 1 時則是 2 的 63 次方,以此類推直到比對到第 2 位元。
```c
/**
* ilog2 - log of base 2 of 32-bit or a 64-bit unsigned value
* @n - parameter
*
* constant-capable log of base 2 calculation
* - this can be used to initialise global variables from constant data, hence
* the massive ternary operator construction
*
* selects the appropriately-sized optimised version depending on sizeof(n)
*/
#define ilog2(n) \
( \
__builtin_constant_p(n) ? ( \
(n) < 2 ? 0 : \
(n) & (1ULL << 63) ? 63 : \
(n) & (1ULL << 62) ? 62 : \
...
(n) & (1ULL << 4) ? 4 : \
(n) & (1ULL << 3) ? 3 : \
(n) & (1ULL << 2) ? 2 : \
1 ) : \
(sizeof(n) <= 4) ? \
__ilog2_u32(n) : \
__ilog2_u64(n) \
)
```
透過 Github 搜尋欄位查詢到在 [linux/lib/math/div64.c](https://github.com/torvalds/linux/blob/master/lib/math/div64.c#L193) 當中有應用到 `ilog2` 這個巨集。
```c
#ifndef mul_u64_u64_div_u64
u64 mul_u64_u64_div_u64(u64 a, u64 b, u64 c)
{
u64 res = 0, div, rem;
int shift;
/* can a * b overflow ? */
if (ilog2(a) + ilog2(b) > 62) {
...
```
`mul_u64_u64_div_u64` 函式當中運用 `ilog2` 巨集來判斷兩數 a, b 是否在相乘之後是否會溢位。
### 測驗五
測驗五是測驗三的延伸,改變的點是添加了 ceilling 的功能。
```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;
}
```
`r` 以及 `shift` 是用作紀錄因給定數字 `x` 分別大於 `0xFFFF`, `0xFF`, `0xF`, `0x3` 的時候,需要進行下一步判斷需要位移的數量。
其中 `r`, `shift` 都是 2 的指數所以 `r | shift` 就相當於 `r + shift`。
最後的 `x > 1` 是為了判斷 `x` 若是大於 1 且小於等於 0x3 時的狀況。
### 測驗五延伸問題
#### 判斷 `x = 0` in branchless
若是 `x` 為 0,在進入函式一開始的時候會因為 `x--` 變成 0xFFFFFFFF,所以在做減法前就需要判斷是否為 0。
```diff
int ceil_ilog2(uint32_t x)
{
uint32_t r, shift;
+ x -= (x > 0);
- x--;
```
> commit: [0095464](https://github.com/brian049/Linux_quiz_code/commit/0095464b9d384b40b2cd85d3e8b1d0199472fb0e)