# 2023q1 Homework2 (quiz2)
contributed by < `joshyue` >
## 測驗 `1`
>參照 [你所不知道的 C 語言: bitwise 操作](https://hackmd.io/@sysprog/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 >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}
```
* 64位元之數值`x`,在此以`x = 0b0100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`為例。
* 經過7次`x |= x >> 1`運算,可保證原先最高位的1後面會有6個1,即`x = 0b0111 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`。
* 再經過`x |= x >> 8`、`x |= x >> 32`、`x |= x >> 64`,則`x = 0b0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111`,如此便能使一開始最高位的1右邊所有位元皆指派為1。
* 此時`x + 1`才為2的冪,因此最後要`return x + 1`,即能得到`next_pow2`所求。
### 用 __builtin_clzl 改寫
>[__builtin_clzl](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) : Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
```c
uint64_t next_pow2(uint64_t x)
{
if (!x)
return 1;
int clz = __builtin_clzl(x);
return (1 << (64 - clz));
}
```
* 當`x`不為`0`時,`__builtin_clzl(x)`會回傳最高位開始連續0的個數的功能,假設`x=1024`時,則`clz = 53`。
* 則可以透過求得的`clz`,回傳`(1 << (64 - clz))`,即得題目所求。
### 在 Linux 核心原始程式碼找出類似的使用案例並解釋
* 在<[include/asm-generic/bitops/builtin-fls.h](https://elixir.bootlin.com/linux/latest/source/include/asm-generic/bitops/builtin-fls.h)>中的`fls`函式,有使用到`__builtin_clz`的功能。
```c
static __always_inline int fls(unsigned int x)
{
return x ? sizeof(x) * 8 - __builtin_clz(x) : 0;
}
```
>fls - find last (most-significant) bit set
>
透過`sizeof(x) * 8`可得到`x`的位元數量,再減去最高位開始連續0的個數,則可以得到`fls`函式所求,即最高位元的1的bit index + 1。
### 上述 clz 內建函式已運用時,編譯器產生之對應x86指令
* 執行`cc -O2 -std=c99 -S next_pow2.c`,觀察所得`next_pow2.s`
```c
movl $1, %eax
testq %rdi, %rdi
je .L1
bsrq %rdi, %rdi
movl $64, %ecx
xorq $63, %rdi
subl %edi, %ecx
sall %cl, %eax
cltq
```
* 觀察`__builtin_clzl(x)`所對應的x86指令,其中 `bsrq %rdi, %rdi` 會將最高位的1之bit index儲存於rdi暫存器。
* 而後的 `xorq $63, %rdi` 則是對`63`與最高位的1之bit index作`Exclusive OR`,則等同於`63 - 1之bit index`,因此得到所求,即最高位開始連續0的個數。
---
## 測驗 `2`
>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;
}
```
* 若要判斷`i`是否為2的冪,現假設`i = 8`,則二進位表示`0b1000`,`i - 1`的二進位表示則為`0b0111`,可觀察到兩者依序對應的bit皆相異,因此當`i`為2的冪時,`i & (i - 1)`必等於0。
* 並且題目所述1 到 n 的二進位表示法依序串接,於是我們可以將目前的字串(`ans`)向左shift所需串接的i之位元長度(`len`)後,再與i作`bitwise OR`,最後再mod $10^9+7$,即為題目所求。
### 使用__builtin_{clz,ctz,ffs}改寫,改進mod $10^9+7$ 的運算
```c
int concatenatedBinary(int n)
{
const int M = 1e9 + 7;
int len = 0; /* the bit length to be shifted */
long ans = 0;
for (int i = 1; i <= n; i++) {
int ffs = __builtin_ffsl(i);
if (!(i >> (ffs)))
len++;
ans = (i | (ans << len)) % M;
}
}
```
>[__builtin_ffsl](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) : Returns one plus the index of the least significant 1-bit of x, or if x is zero, returns zero.
* 原先使用以上的程式碼,以`__builtin_ffsl`來取得最低位開始第一個1的bit index + 1,`i >> (ffs)`若為0,則表示`i`為2的冪,而後發現此程式碼仍可改進,改進如下。
```c
int concatenatedBinary(int n)
{
const int M = 1e9 + 7;
int len = 0; /* the bit length to be shifted */
long ans = 0;
for (int i = 1; i <= n; i++){
ans = (i | (ans << (64 - __builtin_clzl(i)))) % M;
}
}
```
* 以`__builtin_clzl`得到最高位開始連續0個數後,將i往左shift `64 -
__builtin_clzl(i)`(其等同先前的程式碼向左shift `len` ),這樣的改進能省去`if`的分支,不用判斷`i`是否為2的冪,使程式碼更加精簡。
---
## 測驗 `3`
>[SIMD within a register](https://en.wikipedia.org/wiki/SWAR) (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 << 3) * (len / 8) - count;
count += (len & 7) ? count_utf8((const char *) end, len & 7) : 0;
return count;
}
```
### 比較 SWAR 和原本的實作效能落差
> [__builtin_popcountll](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) : Returns the number of 1-bits in x.
* 第13行 : 透過`__builtin_popcountll`求得位元組內1的總數,`conut`即代表題目所述continuation bytes(後續位元組)的個數。
* 第16行 : 因為我們在處理一個qword時,qword大小為8byte,`(len / 8)`即表示完整處理的次數(因後續要判斷剩餘未滿1個qword的狀況),`(1 << 3) * (len / 8)`表示完整處理的次數再乘以8,即已處理字串的長度,並且題目有提到字串長度減去後續位元組,即可確定字元數量,因此我們要再減去第13行得到的`count`,得到字元數量。
* 第17行 : `(len & 7)`等同於`len % 7`,若`len & 7`不為0,表示我們仍有未處理的字元,則呼叫原先的函式`count_utf8`,回傳剩餘的字元數量。
### 在 Linux 核心原始程式碼找出 UTF-8 和 Unicode相關字串處理的程式碼,探討其原理
* 在<[fs/unicode/utf8-core.c](https://elixir.bootlin.com/linux/latest/source/fs/unicode/utf8-core.c#L20)>中,有提供`utf8_strncmp`函式。
* 如果返回值 = 1,則表明兩字串不相等
如果返回值 = 0,則表明兩字串相等
```c
int utf8_strncmp(const struct unicode_map *um,
const struct qstr *s1, const struct qstr *s2)
{
struct utf8cursor cur1, cur2;
int c1, c2;
if (utf8ncursor(&cur1, um, UTF8_NFDI, s1->name, s1->len) < 0)
return -EINVAL;
if (utf8ncursor(&cur2, um, UTF8_NFDI, s2->name, s2->len) < 0)
return -EINVAL;
do {
c1 = utf8byte(&cur1);
c2 = utf8byte(&cur2);
if (c1 < 0 || c2 < 0)
return -EINVAL;
if (c1 != c2)
return 1;
} while (c1);
return 0;
```
* `utf8ncursor`結構表示字串處理的進度,分別透過`c1`及`c2`獲取目前處理字串位置的一個位元組,並轉換此位元組的型態為`unsigned char`,因此若`c1`或`c2`小於0,則表示程式無效,回傳`-EINVAL`,反之的話就是繼續判斷兩個字元是否相等。
---
## 測驗 `4`
>以下程式碼可判定 16 位元無號整數是否符合以下特定樣式 (pattern):
```c
bool is_pattern(uint16_t x)
{
if (!x)
return 0;
for (; x > 0; x <<= 1) {
if (!(x & 0x8000))
return false;
}
return true;
}
```
* 符合上述程式碼的樣式,如下:
```
pattern: 8000 (32768)
pattern: c000 (49152)
pattern: e000 (57344)
pattern: f000 (61440)
pattern: f800 (63488)
pattern: fc00 (64512)
pattern: fe00 (65024)
pattern: ff00 (65280)
pattern: ff80 (65408)
pattern: ffc0 (65472)
pattern: ffe0 (65504)
pattern: fff0 (65520)
pattern: fff8 (65528)
pattern: fffc (65532)
pattern: fffe (65534)
pattern: ffff (65535)
```
* 改寫上述程式碼,使其等價且更精簡有效,改寫內容如下:
```c
bool is_pattern(uint16_t x)
{
const uint16_t n = -x;
return (n ^ x) < x;
}
```
* 透過`x`觀察是否符合程式碼的樣式(pattern)可發現,只要`x`在二進位下符合最高位開始為1的條件,後面依序接著連續的1(可為0個)與連續的0(可為0個),則符合上述的樣式。
* 上述程式碼的`-x`表示`x`的二補數,以下方圖表為例,若x符合樣式,則`(n ^ x)`必定小於`x`,因此回傳`True`。
| x | n = -x | n ^ x |
| -------- | -------- | -------- |
| (1111 0000 0000 0000)~2~ | (0001 0000 0000 0000)~2~ | (1110 0000 0000 0000)~2~ |
| (1111 0100 0000 0000)~2~ | (0000 1100 0000 0000)~2~ | (1111 1000 0000 0000)~2~ |
| (1111 0000 0000 0001)~2~ | (0000 1111 1111 1111)~2~ | (1111 1111 1111 1110)~2~ |
### 在 Linux 核心原始程式碼找出上述 bitmask 及產生器,探討應用範疇