---
tags: linux
---
# 2023q1 Homework2 (quiz2)
contributed by < `kata1219` >
### 測驗 `1`
參照 你所不知道的 C 語言: bitwise 操作,考慮 next_pow2 可針對給定無號 64 位元數值 x,找出最接近且大於等於 2 的冪的值
```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 >> AAAA;
x |= x >> 16;
x |= x >> BBBB;
return CCCC;
}
```
AAAA = 8
BBBB = 32
CCCC = ++x
程式碼也可以改寫為
```c
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;
}
```
> 在 x 為 2 的冪時答案會錯誤,需要提前檢查 x 是否為 2 的冪
#### 延伸問題
1. 解釋上述程式碼原理,並用 `__builtin_clzl` 改寫
> `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.
> `int __builtin_clzl (unsigned long)`
> Similar to `__builtin_clz`, except the argument type is unsigned long.
* 我們需要把最左側為 1 的位元及其右側的位元都設為 1,再加上 1 後即可得到最接近且大於等於 2 的冪。給定的範例程式碼中出現了 7 次 `x |= x >> 1;`,代表 x 中為 1 的位元及其後面 7 位元共 8 位元設為 1。下一行的功能是將這 8 位元的 1 拓展一倍,以此類推,直到將 1 後面的 64 位元都設為 1。
```c
uint64_t next_pow2(uint64_t x)
{
return (1 << 64 - __builtin_clzl(x));
}
```
2. 在 Linux 核心原始程式碼找出類似的使用案例並解釋
*
3. 當上述 clz 內建函式已運用時,編譯器能否產生對應的 x86 指令?
> 提示: 可執行 `cc -O2 -std=c99 -S next_pow2.c` 並觀察產生的 `next_pow2.s` 檔案,確認 bsrq 指令 (表示 “Bit Scan Reverse”)
* 可以,以下為節錄的程式碼。
```
next_pow2:
.LFB13:
.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`
給定一整數 $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 (!(DDDD))
len++;
ans = (i | (EEEE)) % M;
}
return ans;
}
```
DDDD = i ^ (i & ~(i - 1))
EEEE = ans << len
#### 延伸問題
1. 解釋上述程式碼運作原理
* 在程式碼中,每回合 ans 需要空出 i 所佔的位元數後進行合併並取餘數。len 代表 i 所占用的位元數,當 i 是 2 的冪時長度需要加 1。
2. 嘗試使用 `__builtin_{clz,ctz,ffs}` 改寫,並改進 mod $10^9+7$ 的運算
```c
int concatenatedBinary_built_in(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++)
{
if (!(i ^ 1 << __builtin_ctz(i)))
// 或 if(__builtin_clz(i) + __builtin_ctz(i) == 31)
len++;
ans = (i | (ans << len)) % M;
}
return ans;
}
```
> 目前沒想到怎麼用 bitwise operation 實作 mod $10^9+7$
:::warning
參照 [Barrett reduction](https://en.wikipedia.org/wiki/Barrett_reduction)
:notes: jserv
:::
---
### 測驗 `3`
SWAR 實作
```c
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 << AAAA) * (len / 8) - count;
count += (len & BBBB) ? count_utf8((const char *) end, len & CCCC) : DDDD;
return count;
}
```
AAAA = 3
BBBB = 7
CCCC = 7
DDDD = 0
#### 延伸問題
1. 解釋上述程式碼運作原理,比較 SWAR 和原本的實作效能落差
*
2. 在 Linux 核心原始程式碼找出 UTF-8 和 Unicode 相關字串處理的程式碼,探討其原理,並指出可能的改進空間
---
### 測驗 `4`
以下程式碼可判定 16 位元無號整數是否符合特定樣式(在 leftmost set bit 左側的位元皆為 1 且 x 不為 0),將程式碼改寫為使用 bitwise operation。
```c
#include <stdint.h>
#include <stdbool.h>
bool is_pattern(uint16_t x)
{
if (!x)
return 0;
for (; x > 0; x <<= 1) {
if (!(x & 0x8000))
return false;
}
return true;
}
```
```c
bool is_pattern(uint16_t x)
{
const uint16_t n = EEEE;
return (n ^ x) < FFFF;
}
```
EEEE = 0xFFFF
FFFF = (x & ~(x - 1))
#### 延伸問題
1. 解釋上述程式碼運作原理
* 嘗試將 n 設為幾個常見的數,如 0x0000、0xFFFF 等等,代入 n ^ x 發現只要 x 符合特定樣式,在 0xFFFF ^ x 後,皆會變成 2 的冪 - 1,因此我們找出 x 的 rightmost set bit,只要 XOR 後大於等於 rightmost set bit 的都不符合特定樣式。
2. 在 Linux 核心原始程式碼找出上述 bitmask 及產生器,探討應用範疇
> 參見 Data Structures in the Linux Kernel