# 2023q1 Homework2 (quiz2)
contributed by < terry23304 >
## 測驗 1
### 運作原理
```c
uint64_t next_pow2(uint64_t x)
{
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 1;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}
```
2 的冪次方除了最高位的 1 其餘都是 0
把最高位的 1 右邊的 bit 都設為 1,最後加一就可以得到 next_pow
### 用 __builtin_clzl 改寫
unsinged long 跟 uint64_t 等價
`__builtin_clzl(x)` 會回傳最高位的 1 前面有幾個 0 ,減去總長 64 bit 就可以得到要位移的個數
```c
uint64_t new_next_pow2(uint64_t x)
{
if (!x)
return 1;
int n = 64 - __builtin_clzl(x);
return 1 << n;
}
```
### 在 Linux 核心原始程式碼找出類似的使用案例並解釋
參考 [kfifo.c](https://github.com/torvalds/linux/blob/master/lib/kfifo.c)使用 `roundup_pow_of_two` 來保證 `size` 是二的冪次方, `size` 是 circular buffer 的大小,當 `size` 是二的冪次方時可以使用位元運算代替除法跟模數的運算,可以提高 index 計算的效率。
```c
int __kfifo_alloc(struct __kfifo *fifo, unsigned int size,
size_t esize, gfp_t gfp_mask)
{
/*
* round up to the next power of 2, since our 'let the indices
* wrap' technique works only in this case.
*/
size = roundup_pow_of_two(size);
fifo->in = 0;
fifo->out = 0;
fifo->esize = esize;
if (size < 2) {
fifo->data = NULL;
fifo->mask = 0;
return -EINVAL;
}
fifo->data = kmalloc_array(esize, size, gfp_mask);
if (!fifo->data) {
fifo->mask = 0;
return -ENOMEM;
}
fifo->mask = size - 1;
return 0;
}
```
### 當上述 clz 內建函式已運用時,編譯器能否產生對應的 x86 指令?
可以發現編譯器有產生 `bsrq` 指令
```
new_next_pow2:
.LFB14:
.cfi_startproc
endbr64
bsrq %rdi, %rdi
movl $64, %ecx
movl $1, %eax
xorq $63, %rdi
subl %edi, %ecx
sall %cl, %eax
cltq
ret
.cfi_endproc
```
## 測驗 2
### 運作原理
目標: 把 1~n 的十進位轉為二進位,再串接再一起,求出十進位值
`len` 紀錄要左移的位元數
用 `!(i & (i-1))` 檢查 `i` 使否為 2 的冪次方
若 `i` 不是 2 的冪次方則 `len` 不變,因為此時 `i` 的二進位表示法中有多個 1。如果 `i` 是 2 的冪次方,則 `len` 加 1,因為此時 `i` 的二進位表示法中只有一個 1,且其位置在前面,必須左移更多位元才能將其串接起來。
當前結果與前面迴圈結果左移做 logical or 再 mod M 避免溢位
`ans = (i | (ans << len)) % M;`
### 嘗試使用 \_\_builtin_{clz,ctz,ffs} 改寫,並改進 mod $10^9 + 7$ 的運算
- 檢查 `i` 使否為 2 的冪次方
把 `if (!(i & (i-1)))` 改成 `if ((__builtin_clz(i) + __builtin_ctz(i)) == 31)`
代表 32 位元中只有一位是 1
`len++` 可改成 `len = __builtin_ffs(i);` 得到 `i` 的 leading 1 位置
原本為在 2 的冪次的時候 `len++` ,但改用 `__builtin_ffs(i)` 更新 `len`,只有在 2 的冪次方的時候 `len` 才會改變,所以就不用檢查 `i` 是否為 2 的冪次方
```c
for (int i = 1; i <= n; i++) {
len = __builtin_ffs(i);
ans = (i | (ans << len)) % M;
}
```
- 也可用 `if(__builtin_popcount(i) == 1)`
`__builtin_popcount(i)` 會回傳 `i` 中有幾個 bit 是 1
Built-in Function: int __builtin_ffs (int x)
從最低位開始的第一個 1 的位置。如果 x 是 0,則返回 0。
## 測驗 3
### 運作原理
在 UTF-8 編碼中,如果一個字節的最高位是 0,那這個字節所表示的就是 ASCII 字符;如果最高位是 1,那麼它所表示的就是一個多字節序列的開始字節。對於多字節序列的開始字節,最高位的 1 的數量表示了這個序列所包含的字節數量,例如,最高位有 2 個 1 的字節表示的是一個由 2 個字節組成的序列。
`if (p[i] > -65)`可用來判斷一個字節是否是一個多字節序列的開始字節
使用 SWAR 對 64 位數進行運算來加速計算
將 UTF-8 字符串分成若干個 64 位整數,然後對每個整數進行位運算,最後統計所有 64 位整數中表示字符的位數之和,得到最終的字符數量。
計算 buf 總共有幾個 8 byte
```c
const uint64_t *qword = (const uint64_t *) buf;
const uint64_t *end = qword + len >> 3;
```
每次遍歷 8 byte,用以下操作來找到有幾個 10 開頭的 byte
```c
for (; qword != end; qword++) {
const uint64_t t0 = *qword;
const uint64_t t1 = ~t0;
const uint64_t t2 = t1 & 0x04040404040404040llu;
const uint64_t t3 = t2 + t2;
const uint64_t t4 = t0 & t3;
count += __builtin_popcountll(t4);
}
```
上一步操作只處理了整數部分,還要處理不能被 8 整除的部分。
首先計算整數部分中表示字符的位的總數,然後將未處理的字節按照原來的方法進行計算,最後將結果加總起來得到最終的字符數量
```c
count = (1 << 3) * (len / 8) - count;
count += (len & 7) ? count_utf8((const char *) end, len & 7) : 0;
```
## 測驗 4
### 運作原理
要判斷的 pattern 是若有出現 1 則必須從 MSB 開始,且中間不可有0
```c
bool is_pattern(uint16_t x)
{
const uint16_t n = -x;
return (n ^ x) < x;
}
```
```
x: 1101000
-x: 0011000 // 把1跟1之間的0變成1,保留最後的1
-x^x: 1110000 // 把1跟1之間的0變成1,就會大於原本的x
```
### 在 Linux 核心原始程式碼找出上述 bitmask 及產生器,探討應用範疇