---
# System prepended metadata

title: 2023q1 Homework2 (quiz2)
tags: [linux2023]

---

---
tags: linux2023
---

# 2023q1 Homework2 (quiz2)
contributed by < `fennecJ` >

### 測驗 `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;
}
```
### 原理：
將 `x`  與 `x` 右移 1 位元進行或運算， 將 `x` 最高位非零位元（為使說明簡潔，以下所有「最高位元」皆指「最高位非零位元」）右側的位元設為 1 並將結果存回 `x` 。
重複執行上述步驟 7 次，包含最高位共會有八個位元被設為 1 。
因此下一步可以直接將 `x`  與 `x` 右移 8 位元進行或運算，將 `x` 最高位元右側的 16 個位元設為 1。
依此類推，分別將 `x` 與右移 16 位元以及 32 位元的 `x` 進行或運算。最多可將 `x` 最高位元右側的 64 個位元設為 1 ，由於 `x` 本身為 `uint64_t` 型別的變數，因此可以確保從 `x` 最高位到最低位的所有位元皆被設為 1 。
之後將 x 加上 1 ，即可得到比 x 大的下一個 2 的冪。

### 進一步改進：
由於 `x`  與 `x` 右移 1 位元進行或運算後能使最高 2  位元被設為 1 ，因此接下來可以一次右移兩個位元。
依此類推，在下一次可以一次右移 4 、 8 、 16 、 32 個位元。
```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 + 1;
}
```
### 改以 __builtin_clzl 實做上述函式：
`__builtin_clz (unsigned int x)` 的用途為： 
`Returns the number of leading 0-bits in x` 即返回最高位的非零位元之前有幾個位元為 0 。
而 `__builtin_clzl(unsigned long x)` 則為 `__builtin_clz` 的 `unsigned long` 版。

我們可以用這個函式改進 next_pow2 的實做：
```c 
uint64_t next_pow2(uint64_t x)
{
    return (uint64_t) 1 << (64 - __builtin_clzl(x));
}
```
不過上述作法可能會導致問題，關於 `__builtin_clz (unsigned int x)` 的敘述還有一段：
`If x is 0, the result is undefined.`
我們可以進一步將其改為：
```c 
uint64_t next_pow2(uint64_t x)
{
    if(!x)
        return 1;
    return (uint64_t) 1 << (64 - __builtin_clzl(x));
}
```
不過這樣 code 便會產生 branch ，嘗試將其改為 branchless 的實做：
```c 
uint64_t next_pow2(uint64_t x)
{
    // !x can only be either `1` or `0`.
    // if(x == 0), (x | !x) = 1
    // else (x | !x) = x;
    int fix = (x | !x) - x; // fix = (x == 0) ? 1 : 0;
    uint64_t leads = __builtin_clzl(x | 1); 
    leads += fix;
    return (uint64_t) 1 << ((uint64_t)64 - leads);
}
```
以 perf 比較有 branch 和無 branch 計算 0 ~ (1 << 32) 的 next_pow2 時的執行效能：
```c 
for(uint64_t u = 0; u <= (uint64_t) 1 << 32; u++){
    next_pow2(u);
}
```
```shell 
# gcc branchLess.c
$ sudo perf stat --repeat 5 -e cache-misses,cache-references,instructions,cycles,branch-misses ./a.out
---
output:
 Performance counter stats for './a.out' (5 runs):

         1,920,238      cache-misses              #   89.358 % of all cache refs      ( +- 18.46% )
         3,358,733      cache-references                                              ( +- 19.68% )
   158,943,820,480      instructions              #    2.80  insn per cycle           ( +-  0.01% )
    56,698,618,164      cycles                                                        ( +-  0.06% )
           143,546      branch-misses                                                 ( +- 27.52% )

            14.677 +- 0.108 seconds time elapsed  ( +-  0.73% )

```

```shell 
# gcc branched.c
$ sudo perf stat --repeat 5 -e cache-misses,cache-references,instructions,cycles,branch-misses ./a.out
---
output:
 Performance counter stats for './a.out' (5 runs):

           373,336      cache-misses              #   31.788 % of all cache refs      ( +- 27.43% )
           757,761      cache-references                                              ( +- 52.57% )
   103,092,710,628      instructions              #    2.79  insn per cycle           ( +-  0.01% )
    36,953,381,047      cycles                                                        ( +-  0.08% )
            55,425      branch-misses                                                 ( +- 61.77% )

            9.6003 +- 0.0237 seconds time elapsed  ( +-  0.25% )
```


可以看到雖然 branchLess 的 branch misses 比較少（應該都是 for loop 是否結束的判斷而已），但他花的時間 (14.61s) 卻比 branched 花的時間 (9.66s) 長。
看到 instructions cnt  
branchLess 的實做雖然有較少的 branch misses，但它每個 funct call 要執行的指令較多，共需執行 `158933887154` 個指令，相較之下，有 branch 的實做僅須執行 `103140140758` 個指令。
$$\frac{158933887154-103140140758}{103140140758} = 0.541$$
branchLess 的實做要比 branched 多 54 ％ 的指令要執行。

$$\frac{9.6}{14.67} = 0.65$$

branched 所花執行時間僅為 branchLess 的 65 ％ 。

以下是兩種實做編譯後的結果：
branchLess:
```=
next_pow2_branchLess:
        push    {r4, r5, r6, r7, r8, r9, r10, fp}
        sub     sp, sp, #24
        add     r7, sp, #0
        strd    r0, [r7]
        ldr     r0, [r7]
        ldr     r1, [r7, #4]
        orrs    r1, r1, r0
        cmp     r1, #0
        ite     eq
        moveq   r1, #1
        movne   r1, #0
        uxtb    r1, r1
        mov     r0, r1
        ldr     r1, [r7]
        mvns    r1, r1
        ands    r1, r1, r0
        str     r1, [r7, #20]
        ldr     r1, [r7]
        orr     r1, r1, #1
        clz     r1, r1
        asrs    r0, r1, #31
        mov     r10, r1
        mov     fp, r0
        strd    r10, [r7, #8]
        ldr     r1, [r7, #20]
        asrs    r0, r1, #31
        mov     r4, r1
        mov     r5, r0
        ldrd    r0, [r7, #8]
        adds    r8, r0, r4
        adc     r9, r1, r5
        strd    r8, [r7, #8]
        ldr     r1, [r7, #8]
        rsb     r4, r1, #64
        mov     r0, #1
        mov     r1, #0
        sub     r6, r4, #32
        rsb     r5, r4, #32
        lsl     r3, r1, r4
        lsl     r6, r0, r6
        orrs    r3, r3, r6
        lsr     r5, r0, r5
        orrs    r3, r3, r5
        lsl     r2, r0, r4
        mov     r0, r2
        mov     r1, r3
        adds    r7, r7, #24
        mov     sp, r7
        pop     {r4, r5, r6, r7, r8, r9, r10, fp}
        bx      lr
```

branched:
```=
next_pow2_branched:
        push    {r4, r5, r6, r7}
        sub     sp, sp, #8
        add     r7, sp, #0
        strd    r0, [r7]
        ldrd    r0, [r7]
        orrs    r1, r1, r0
        bne     .L2
        mov     r2, #1
        mov     r3, #0
        b       .L3
.L2:
        ldr     r1, [r7]
        clz     r1, r1
        rsb     r4, r1, #64
        mov     r0, #1
        mov     r1, #0
        sub     r6, r4, #32
        rsb     r5, r4, #32
        lsl     r3, r1, r4
        lsl     r6, r0, r6
        orrs    r3, r3, r6
        lsr     r5, r0, r5
        orrs    r3, r3, r5
        lsl     r2, r0, r4
.L3:
        mov     r0, r2
        mov     r1, r3
        adds    r7, r7, #8
        mov     sp, r7
        pop     {r4, r5, r6, r7}
        bx      lr
```


可以明顯看到 branched 即便在最長的分枝（2~6->L2) 下需要執行的指令數量仍顯著比 branchLess 少。

題外話：
原先我嘗試用
```c 
fix = (1 >> x)
```
實做上述 branchLess 的函式，想法是只有在 x 為 0 時 fix 才會是 1 ，其餘時候 fix 都會是 0 。
但執行後發現在 x = 64 時結果不正確，才知道原來右移的數量 >= 1 的位寬時會造成 undefined behavior[^1] ：
```!
If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
```

最後附上原本以分別右移 1 、 2 、 4 、 8 、 16 、 32 位元實做 `next_pow2` 的執行時間為 (23.41s)
```shell 
# gcc naive.c
$ sudo perf stat --repeat 5 -e cache-misses,cache-references,instructions,cycles,branch-misses ./a.out
---
output:
 Performance counter stats for './a.out' (5 runs):

         1,667,730      cache-misses              #   69.119 % of all cache refs      ( +-  8.41% )
         2,966,903      cache-references                                              ( +-  7.51% )
   141,776,719,950      instructions              #    1.56  insn per cycle           ( +-  0.00% )
    90,925,330,702      cycles                                                        ( +-  0.01% )
           168,508      branch-misses                                                 ( +-  5.54% )

           23.7229 +- 0.0316 seconds time elapsed  ( +-  0.13% )
```


三者比較如下：
```vega

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {
    "values":[
      {
        "yname": "branchLess w/o clzl",
        "time" : 23.41831
      },
      {
        "yname": "branchLess w/ clzl",
        "time" : 14.6
      },
      {
        "yname": "branched w/ clzl",
        "time" : 9.66
      }
    ]
  },
  "height": {"step": 50},
  "mark": {"type": "bar", "height": 23},
  "encoding": {
    "y": {
      "field": "yname",
      "scale": {"padding": 0},
      "title" : ""
    },
    "x": {
      "field": "time", "type": "quantitative",
      "axis": {"grid": true},
      "title": "time(s)"
    }
  }
}
```

### 在 linux 中類似的使用案例
在 [include/linux/log2.h](https://elixir.bootlin.com/linux/v6.2.2/source/include/linux/log2.h#L156) 有一個 MACRO `ilog2` ：
```c=
#define ilog2(n) \
( \
	__builtin_constant_p(n) ?	\
	((n) < 2 ? 0 :			\
	 63 - __builtin_clzll(n)) :	\
	(sizeof(n) <= 4) ?		\
	__ilog2_u32(n) :		\
	__ilog2_u64(n)			\
 )
```
`__builtin_constant_p(expr)` 的用途是判斷 expr 是否為在編譯期就能確定的常數，如果是的話就回傳 1 ，無法確定是否為常數的話就回傳 0
```!
The function returns the integer 1 if the argument is known to be a compile-time constant and 0 if it is not known to be a compile-time constant. A return of 0 does not indicate that the value is not a constant, but merely that GCC cannot prove it is a constant with the specified value of the -O option. 
```
MACRO `ilog2` 做的事情便是用 63 - __builtin_clzll(n) 算出其 log2 的結果，注意上述 MACRO 中第 4 行的 `(n) < 2 ? 0 : ` ，這裡之所以把 `n < 2` 另外處理的原因是為了避免 `n = 0` 時的 undefined behavior 造成問題。


### 編譯後是否會產生 __builtin_clz 對應的組語？
以 godblod 檢視產生的組語： https://godbolt.org/z/ber7hKbYM
```
next_pow2:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        cmp     QWORD PTR [rbp-8], 0
        jne     .L2
        mov     eax, 1
        jmp     .L3
.L2:
        bsr     rax, QWORD PTR [rbp-8]
        xor     rax, 63
        mov     edx, eax
        mov     eax, 64
        sub     eax, edx
        mov     edx, 1
        mov     ecx, eax
        sal     rdx, cl
        mov     rax, rdx
.L3:
        pop     rbp
        ret
```


可以在 `.L2:` 看到產生了對應的 `bsr`

改以 ARM 架構進行編譯：
```
next_pow2:
        push    {r4, r5, r6, r7}
        sub     sp, sp, #8
        add     r7, sp, #0
        strd    r0, [r7]
        ldrd    r0, [r7]
        orrs    r1, r1, r0
        bne     .L2
        mov     r2, #1
        mov     r3, #0
        b       .L3
.L2:
        ldr     r1, [r7]
        clz     r1, r1
        rsb     r4, r1, #64
        mov     r0, #1
        mov     r1, #0
        sub     r6, r4, #32
        rsb     r5, r4, #32
        lsl     r3, r1, r4
        lsl     r6, r0, r6
        orrs    r3, r3, r6
        lsr     r5, r0, r5
        orrs    r3, r3, r5
        lsl     r2, r0, r4
.L3:
        mov     r0, r2
        mov     r1, r3
        adds    r7, r7, #8
        mov     sp, r7
        pop     {r4, r5, r6, r7}
        bx      lr
```

可以看到產生了對應的 `clz`

而若以未使用  __builtin_clzl 實做的函式進行編譯，即便下了 -O3 flag 產生出來的組語也沒有 `bsr` 。

### 測驗 `2`
```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;
}
```
### 原理：
在這個函式中，變數 len 用來表示在將新數字接在 ans 後面時，需要留多少位元給新數字。因此，我們需要找出一個方法來確定要留下多少位元。通過觀察下表，可以發現當 i 恰等於 2 的冪時，len 才需要增加。如下表所示：
| dec | bin(shortest) |len|
| ----- | ------ | ----|
| 1     | 1      |1 |
| 2     | 10     |**2** |
| 3     | 11     |2 |
| 4     | 100    |**3** |
| 5     | 101    |3 |

因此，我們可以利用 !(i & (i - 1)) 來判斷當 i 等於 2 的冪時，讓 len 加 1。因為只有當 i 是 2 的冪時，!(i & (i - 1)) 才會返回 true。

### 以 `__builtin_{clz,ctz,ffs}` 改寫
可以用 `(__builtin_clz(i) + __builtin_ctz(i) == 31)` 取代 `!(i & (i - 1))`
也可以用 `__builtin_popcount(i) == 1` 取代。但這樣還是有 branch，可以直接用 `32 - __builtin_clz(i)` 取代本來的 len 改為 branchLess 的實做：
```c 
int concatenatedBinary(int n)
{
    const int M = 1e9 + 7;
    long ans = 0;
    for (int i = 1; i <= n; i++) {
        ans = (i | (ans << (32 - __builtin_clz(i)))) % M;
    }
    return ans;
}
```

### 改善 ％ 1e9 + 7 運算
#### 以 barrett_reduce[^2] 改善


$$ a \% b = a − \lfloor a/b \rfloor * b $$
其中 
$$ \lfloor a/b \rfloor 
\approx ((\lfloor (2^{64}-1) /b \rfloor)*a) / 2^{64} $$

$$ \therefore a \% b  \approx
a − ((\lfloor (2^{64}-1) /b \rfloor)*a) / 2^{64} * b
$$
當 $((⌊(2^{64}−1)/b⌋)∗a)$ 精度夠高時，其計算結果與實際執行 mod 運算無異。
我們可以用這個近似來做快速 modulo 運算，以 `__uint128_t`增加 $((⌊(2^{64}−1)/b⌋)∗a)$ 的精度 ：
```c 
#define b 1000000007

uint64_t reduce(uint64_t a) {
  __uint128_t p = (__uint128_t)(-1ULL / b) * a;
  uint64_t r = (uint64_t)(p >> 64);
  return a - r * b;
}
```
上述程式碼中，`-1ULL` $= 2^{64} - 1$
該實做在 leedcode 上能跑過。

以 perf 檢視進行 50000 次隨機數字的 mod 運算效能比較，為了避免額外變因把 srand 的 seed 設為 50 ：
```c 
int main()
{
  srand(50);
  for (int i = 0; i < 50000; i++) {
    uint64_t a = reduce(rand());
  }
  return 0;
}
```
### 直接 % 1e9+7
```c
uint64_t reduce(uint64_t a) { return a % b; }
```
```bash
sudo perf stat --repeat 5 -e instructions,cycles ./a.out 

 Performance counter stats for './a.out' (5 runs):

         4,991,514      instructions              #    1.27  insn per cycle           ( +-  0.07% )
         3,888,311      cycles                                                        ( +-  0.43% )

          0.003012 +- 0.000692 seconds time elapsed  ( +- 22.97% )
```

### 使用 barrett_reduce
```c
uint64_t reduce(uint64_t a) {
  __uint128_t p = (__uint128_t)(-1ULL / b) * a;
  uint64_t r = (uint64_t)(p >> 64);
  return a - r * b;
}
```
```bash
sudo perf stat --repeat 5 -e instructions,cycles ./a.out 

 Performance counter stats for './a.out' (5 runs):

         5,797,471      instructions              #    1.41  insn per cycle           ( +-  0.04% )
         4,080,097      cycles                                                        ( +-  0.56% )

         0.0018122 +- 0.0000300 seconds time elapsed  ( +-  1.66% )

```
可以看到執行時間 $\dfrac{0.0018122}{0.003012}\approx 60\%$ 約變為本來的 60 ％ 。

:::info
為何除法運算比 mod 快 ？
compiler 裡面有很多特別針對除法進行的操作能大幅加速除法的速度，但這些操作不一定能用在 mod 上 。
:::



### 測驗 `3`
```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;
}
```




---
Ref:
[GCC Other-Builtin functs](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)
[Compiler Explorer](https://godbolt.org/)
[Linux 效能分析工具: Perf](http://wiki.csie.ncku.edu.tw/embedded/perf-tutorial)
[^1]: [ISO/IEC 9899:1999 (E)](https://www.dii.uchile.cl/~daespino/files/Iso_C_1999_definition.pdf) §6.5.7
[^2]: [Computing binomial coefficients modulo
integers quickly](https://simonlindholm.github.io/files/bincoef.pdf)