# 2023q1 Homework2 [(quiz2)](https://hackmd.io/@sysprog/linux2023-quiz2)
contributed by < `tseng0201` >
## 測驗一
考慮 next_pow2 可針對給定無號 64 位元數值 x,找出最接近且大於等於 2 的冪的值,例如:
以下是可能的實作方式:
```c
#include <stdint.h>
static inline uint64_t pow2(uint8_t e) { return ((uint64_t)1) << e; }
uint64_t next_pow2(uint64_t x)
{
uint8_t lo = 0, hi = 63;
while (lo < hi) {
uint8_t test = (lo + hi)/2;
if (x < pow2(test)) { hi = test; }
else if (pow2(test) < x) { lo = test+1; }
else { return pow2(test); }
}
return pow2(lo);
}
```
我們嘗試改寫為以下:
```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;
}
```
### 1.解釋程式碼原理,並用 __builtin_clzl 改寫
解釋上述程式碼原理
第一個方法使用二分搜尋演算法,首先將下界與上界設為 0 與 63,分別代表 $2^{0}$ 與 $2^{63}$
```
uint8_t lo = 0, hi = 63;
```
每次取下界與上界的中間值(test)與 $x$ 做比較
```
uint8_t test = (lo + hi)/2;
```
比較的結果有 3 種可能,x = test、x < test、x > test,當 x = test 回傳 test,其餘調整上界或下界直到 lo == hi 回傳 lo
```
if (x < pow2(test)) { hi = test; }
else if (pow2(test) < x) { lo = test+1; }
else { return pow2(test); }
```
第二種方法也是一種採用二分法的算法,透過位元位移與或運算,從輸入值二進制表示中最高位出現的1的位置開始,將其以下所有位數設為1,這樣就可以得到一個大於輸入值x但不大於最接近2的冪的值。接著,將輸入值x加1再進行進位操作,就可以得到大於等於最接近2的冪的值。**此外此方法對於 x 剛好等於 2 的冪時會產生錯誤**
上述方法可以簡化為以下程式碼。且為了修正當 x 剛好等於 2 的冪時產生的錯誤,我們將輸入值 x 減 1。當於x不是2的冪的情況,將其減1不會影響最高位1出現的位置。而對於 x 為 2 的冪的情況,x 減 1 經過或運算並不會改變其值,最後回傳時 x 加 1 又將 x 變回輸入時的數值
```
uint64_t next_pow2(uint64_t x)
{
if (!x)
return 1;
x = x - 1;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}
```
改用 __builtin_clzl 後程式碼如下
```
uint64_t next_pow2(uint64_t x)
{
// if x = 0, result of __builtin_clzl(x) is undefined.
if (!x) return 1;
return 1 << (63 - __builtin_clzl(x));
}
```
根據 [gcc](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 所述當輸入為 0 時會出現未定義行為
```
Built-in Function: int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
Built-in Function: int __builtin_ctzl (unsigned long)
Similar to __builtin_ctz, except the argument type is unsigned long.
```
### 2.在 Linux 核心原始程式碼找出類似的使用案例並解釋 - roundup_pow_of_two()
本習題透過 Chatgpt 完成,詢問內容如下
將程式碼餵給Chatgpt 後詢問函式功能
```
my input:
what this code doing in linux kernel
uint64_t next_pow2(uint64_t x)
{
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}
```
確認 Chatgpt 回答正確後在詢問 linux kernel 是否有相似的函式得到 roundup_pow_of_two()
```
my input:
dose linux kernel has some similiar function?
```
最後透過查詢 Linux github 確認回答是否屬實
該函式位於 include/linux/log2.h
```
roundup_pow_of_two()
/**
* roundup_pow_of_two - round the given value up to nearest power of two
* @n - parameter
*
* round the given value up to the nearest power of two
* - the result is undefined when n == 0
* - this can be used to initialise global variables from constant data
*/
#define roundup_pow_of_two(n) \
( \
__builtin_constant_p(n) ? ( \
(n == 1) ? 1 : \
(1UL << (ilog2((n) - 1) + 1)) \
) : \
__roundup_pow_of_two(n) \
)
fls_long() 位於
```
### 3.當上述 clz 內建函式已運用時,編譯器能否產生對應的 x86 指令?
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
將以下程式碼透過 gcc -O2 -std=c99 -S test.c 進行編譯
```
#include <stdint.h>
#include <stdio.h>
static inline uint64_t pow2(uint8_t e) { return ((uint64_t)1) << e; }
uint64_t next_pow2(uint64_t x)
{
uint8_t lo = 0, hi = 63;
while (lo < hi) {
uint8_t test = (lo + hi)/2;
if (x < pow2(test)) { hi = test; }
else if (pow2(test) < x) { lo = test+1; }
else { return pow2(test); }
}
printf("%d \n", lo);
return pow2(lo);
}
uint64_t next_pow2(uint64_t x)
{
return 1 << ( 63 - __builtin_clzl(x));
}
int main() {
uint64_t input = pow2(10);
printf("%lu, %lu", next_pow3(input), input);
}
```
next_pow2 函式表示如下
```
next_pow2:
.LFB14:
.cfi_startproc
endbr64
// find significant bit index, index start from 0
bsrq %rdi, %rdi
movl $63, %ecx
movl $1, %eax
// number of leading zero = 63 - significant bit index = 63 ^ significant bit index
xorq $63, %rdi
subl %edi, %ecx
sall %cl, %eax
cltq
ret
.cfi_endproc
```
## 測驗二
LeetCode [1680. Concatenation of Consecutive Binary Numbers](https://leetcode.com/problems/concatenation-of-consecutive-binary-numbers/) 給定一整數 $n$ ,回傳將 1 到 $n$ 的二進位表示法依序串接在一起所得到的二進位字串,其所代表的十進位數字 mod $10 ^ 9 + 7$ 之值。
以下是可能的實作:
```c
int concatenatedBinary(int n)
{
const int M = 1e9 + 7;
int len = 0; /* the bit length to be shifted */
/* use long here as it potentially could overflow for int */
long ans = 0;
for (int i = 1; i <= n; i++) {
// removing the rightmost set bit
// e.g. 100100 -> 100000
// 000001 -> 000000
// 000000 -> 000000
// after removal, if it is 0, then it means it is power of 2
// as all power of 2 only contains 1 set bit
// if it is power of 2, we increase the bit length
if (!(i & (i - 1)))
len++;
ans = (i | (ans << len)) % M;
}
return ans;
}
```
### 1.解釋程式碼運作原理
上述程式碼將由 1 到 n 透過 len 的長度控制目前的數字最短可以用多少 bit 表示當 i 為 2 的冪時,代表此後的數字必定要在多一位來表達,故 len + 1,最後透過向左位移 len 個位元來與 i 進行拼接
### 2.嘗試使用 __builtin_{clz,ctz,ffs} 改寫,並改進 mod $10^9 + 7$的運算
使用 __builtin_{clz,ctz,ffs} 改寫如下
```c
int concatenatedBinary(int n)
{
const int M = 1e9 + 7;
int len = 0; /* the bit length to be shifted */
/* use long here as it potentially could overflow for int */
long ans = 0;
for (int i = 1; i <= n; i++) {
ans = (i | ans << (32 -__builtin_clz(i))) % M;
}
return ans;
}
```
TODO: 改進 mod $10^9 + 7$的運算
## 測驗三
### 1.解釋下列程式碼運作原理,比較 SWAR 和原本的實作效能落差
```
size_t swar_count_utf8(const char *buf, size_t len)
{
const uint64_t *qword = (const uint64_t *) buf;
const uint64_t *end = qword + len >> 3;
size_t count = 0;
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);
}
count = (1 << 3) * (len / 8) - count;
count += (len & 7) ? count_utf8((const char *) end, len & 777) : DDDD;
return count;
}
```
上述程式碼透過 SWAR 技術一次性檢查 8 個 Byte 計算給定的輸入共有多少個 UTF-8 字元,依範例所提及合法 UTF-8 的字元共有 4 種不同的長度,除了開頭不同外後面都接著 10xxxxxx 格式,因此透過特定的 Mask 統計 10xxxxxx 的格式所出現的次數,將字串長度(len)扣掉 10xxxxxx 的格式出現次數後,便是此字串所含有的 UTF-8 字元數,最後字串長度可能無法整除 8,所以最後剩餘未檢查的字串需以 count_utf8 進行確認
### 2.在 Linux 核心原始程式碼找出 UTF-8 和 Unicode 相關字串處理的程式碼,探討其原理,並指出可能的改進空間
TODO
## 測驗四
#### 1.解釋下述程式碼運作原理
```
bool is_pattern(uint16_t x)
{
const uint16_t n = -1;
return (n ^ x) < x;
}
```
TODO
#### 2.在 Linux 核心原始程式碼找出上述 bitmask 及產生器,探討應用範疇
[參見 Data Structures in the Linux Kernel](https://0xax.gitbooks.io/linux-insides/content/DataStructures/linux-datastructures-3.html)
TODO