--- tags: 進階電腦系統理論與實作, NCKU Linux Kernel Internals, 作業系統 --- # 2020q3 Homework3 (quiz3) contributed by <`RusselCK` > ###### tags: `RusselCK` > [數值系統 & Bitwise 練習題](https://hackmd.io/@sysprog/2020-quiz3) > [作業繳交區](https://hackmd.io/@sysprog/2020-homework3) ## Q1.在 [二補數系統](https://hackmd.io/@sysprog/binary-representation) 實作跨平台 有號整數的 arithmetic right shift ( ASR ) 在 C 語言中,對有號型態的物件進行算術右移 (arithmetic right shift,以下簡稱 ASR 或 asr) 歸屬於 [unspecified behavior](https://hackmd.io/@sysprog/c-undefined-behavior) ```c= int asr_i(signed int m, unsigned int n) { const int logical = (((int) -1) OP1) > 0; // OP1 = >>1 unsigned int fixu = -(logical & (OP2)); // OP2 = m<0 int fix = *(int *) &fixu; return (m >> n) | (fix ^ (fix >> n)); } ``` 提示: 透過 gcc/clang 編譯程式碼時,可加上 `-fsanitize=undefined` 編譯參數,在執行時期若遇到未定義行為,會得到以下錯誤訊息: >runtime error: load of misaligned address 0x7ffd8a89be8f for type ‘int’, which requires 4 byte alignment > 以 8 bits 為例: > -5 >> 1 = ( 1111 1011 ) >>1 = ( ==1==111 1101 ) = -3 * 程式碼解析: * 我們並不知道電腦進行負整數 ARS 之後,究竟會在空位 補 0 或 補 1 * 我們想要 ==負整數 ARS 後,電腦在空位 補 1== * 根據上述推測 * 如果電腦照我們的想要的方式補 0 或 1 , `(fix ^ (fix >> n))` 可為 0 或 正確的 mask * 反之,`(fix ^ (fix >> n))` 必須為正確的 mask ,將補位換成我們想要的值 >正確的 mask : ( n = 1 ) >fix = ( 1111 1111 ) >`(fix ^ (fix >> n))` = ( ( 1111 1111 ) ^ ( 0111 1111 ) ) = ( 1000 0000 ) ```c #3 const int logical = (((int) -1) OP1) > 0; ``` > -1 = (1111 1111) * 想知道電腦在負數右移會如何補位,可以利用 `#3` 來得知 ( 只須右移 1 位便可得知,故 **OP1 = >>1** ) * 若 `((int) -1) >>1)` 為 ( 0111 1111 ) ,代表電腦在負整數 ARS 會補 0,`logical` 為 `true` = ( 0000 0001 ) ( i.e. 此電腦的右移為 邏輯右移 ) * 若 `((int) -1) >>1)` 為 ( 1111 1111 ) ,代表電腦在負整數 ARS 會補 1,`logical` 為 `false` = ( 0000 0000 ) ( i.e. 此電腦的右移為 算術右移 ) :::success $§\space 6.5.7\space Bitwise \space shift \space operators\space$( p. 85 ) The result of **E1 >> E2** is **E1** right-shifted **E2** bit positions. ==If **E1** has an unsigned type or if **E1** has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of **E1** / $2^{E2}$ . ( i.e. 正整數 ARS 不會出錯 )== If **E1** has a signed type and a negative value, the resulting value is implementation-defined. ::: ```c #4 unsigned int fixu = -(logical & (OP2)); ``` :::success ```c unsigned int a = -9; printd("%d\n", a); ``` * output : ``` -9 ``` ::: * 若 m < 0 且 `logical` = `true` ( i.e. 電腦在負數 ARS 補位的方式不正確 ) * 我們想讓 `fixu` 為 ( 1111 1111 ) * `-(logical & (OP2))` = ( 1111 1111 ) * `(0000 0001) & OP2` = ( 0000 0001 ) `// 2's complement` * OP2 = ( 0000 0001 ) = `true` * 若 m >= 0 且 `logical` = `false` * 我們想讓 `fixu` 為 ( 0000 0000 ) * `-(logical & (OP2))` = ( 0000 0000 ) * `(0000 0001) & OP2` = ( 0000 0000 ) `// 2's complement and overflow` * OP2 = ( 0000 0000 ) = `false` * 根據上面推導,可假設**OP2 = `m < 0`** * 若 `logical` = `false`,OP2 = `m < 0` 仍成立 :::warning 問題:( Form [RinHizakura 同學](https://hackmd.io/NfUj_xJdRDKWeMMJuR2imQ?view) ) ```cpp unsigned int fixu = -(logical & (m < 0)); int fix = *(int *) &fixu; ``` 和 ```cpp int fix = -(logical & (m < 0)); ``` 兩個寫法的差異性? > 前者可避免編譯器進行過度 (aggressive) 最佳化 > :notes: jserv ::: ## Q1. 進階題 #### 實作其他資料寬度的 ASR * 參照 [C 語言:前置處理器應用篇](https://hackmd.io/@sysprog/c-preprocessor) 撰寫通用的巨集以強化程式碼的共用程度 ```cpp= #define asr_i(m,n) \ _Generic((m), \ int8_t: asr_i8, \ int16_t: asr_i16, \ int32_t: asr_i32, \ int64_t: asr_i64 \ )(m,n) #define asr(type) \ const type logical = (((type) -1) >> 1) > 0; \ u##type fixu = -(logical & (m < 0)); \ type fix = *(type *) &fixu; \ return (m >> n) | (fix ^ (fix >> n)) int8_t asr_i8(int8_t m, unsigned int n){ asr(int8_t);} int16_t asr_i16(int16_t m, unsigned int n){ asr(int16_t); } int32_t asr_i32(int32_t m, unsigned int n){ asr(int32_t); } int64_t asr_i64(int64_t m, unsigned int n){ asr(int64_t); } int main(){ ... asr_i(m, n); ... } ``` * `#1`~`#7` : 根據不同的 [data model](https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models),`asr_i(m, n)` 會使用相對應的 function * `#15`~`#18`中的`asr(type)` 會轉換成相對應的內容 ( i.e. `#10`~`#13` ) :::warning 問題:( Form [RinHizakura 同學](https://hackmd.io/NfUj_xJdRDKWeMMJuR2imQ?view) ) 乍想之下,無論型別都直接轉型成 64 bits 的版本處理,計算出結果後再轉回原本的型別,似乎正確性上是沒有問題的?這樣想是否完全正確呢?排除正確性的話,這樣方式的實作可能有甚麼缺點? > 不是每款 C 語言編譯器都能正確處理 64 位元,且 data model 會涉及 ABI 議題 > :notes: jserv ::: ## Q2. LeetCode [342. Power of Four](https://leetcode.com/problems/power-of-four/) (`__builtin_ctz` 實作) * 假設 `int` 為 32 位元寬度 ```c= bool isPowerOfFour(int num) { return num > 0 && (num & (num - 1))==0 && !(__builtin_ctz(num) OPQ); // OPQ = & 0x1 } ``` :::info [GCC extension](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 其中兩個是 ctz 和 clz: * **Built-in Function: `int builtin_ctz (unsigned int x)`** * **Returns the number of trailing 0-bits in x, starting at the least significant bit position.** ( 返回右起第一個 1 之後的 0 的個數 ) * If x is 0, the result is undefined. * **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.** ( 返回左起第一個 1 之前 0 的個數 ) * If x is 0, the result is undefined. ::: * 觀察 $4^n$: | $4^n$ | $4^n$ in binary | __builtin_ctz($4^n$) | |:----- |:--------------- |:-------------------- | | $4^0$ | 0000 000==1== | 0 | | $4^1$ | 0000 0==1==00 | 2 | | $4^2$ | 000==1== 0000 | 4 | | $4^3$ | 0==1==00 0000 | 6 | | $4^k$ | ... | ==2$k$== | * 程式碼解析: ```c num > 0 ``` * 若 $4^n$ 為整數,則 $4^n$ 必大於 0 ```c (num & (num - 1))==0 ``` * 我們可以發現, $4^n$ 在 二進制表示中,只會有 1 個 1 > 以 $4^2$ 為例: > (num & (num - 1)) = (( 0001 0000 ) & ( 0000 1111 ) ) = ( 0000 0000 ) ```c !(__builtin_ctz(num) OPQ) ``` * 我們可以發現,__builtin_ctz($4^n$) 的值必為偶數 * 必為偶數 = 不為奇數 = 第 1 個 bit 不為 1 * 因此,**OPQ = & 0x1** * [效能分析](https://leetcode.com/submissions/detail/403008463/): ![](https://i.imgur.com/L63w2vR.png) ## Q2. 進階題 #### 改寫上述程式碼,提供等價功能的不同實作,儘量降低 branch 的數量 ```cpp= bool isPowerOfFour(int num) { return (__builtin_popcount(num) == 1) && !(__builtin_ctz(num) & 0x1); } ``` * 觀察$4^n$,我們可以發現,只要二進制形式符合下列兩個條件,該數便是 4 的冪 * 只有 1 個 1 ,其餘全為 0 * 1 後面 0 的數量必為 偶數 * [效能分析](https://leetcode.com/submissions/detail/403010290/): ![](https://i.imgur.com/9DygbrT.png) * 其他的改寫: * [RinHizakura 同學](https://hackmd.io/NfUj_xJdRDKWeMMJuR2imQ?view#%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AF%A6%E4%BD%9C) :::info * **`int builtin_ffs (unsigned int x)`** * Returns one plus the index of the least significant 1-bit of x ( 返回右起第一個 1 的位置 ) * if x is zero, returns zero. ::: #### LeetCode [1009. Complement of Base 10 Integer](https://leetcode.com/problems/complement-of-base-10-integer/) ( 利用 clz ) * For a given number N in base-10, return the complement of it's binary representation as a base-10 integer. * Note that except for N = 0, there are no leading zeroes in any binary representation. ```cpp= int bitwiseComplement(int N){ if (N == 0) return 1; int mask = (1 << (32 - __builtin_clz(N))) - 1; return ~N & mask; } ``` >Input: 5 Output: 2 Explanation: 5 is "101" in binary, with complement "010" in binary, which is 2 in base-10. * 程式碼解析: ```cpp= #2 if (N == 0) #3 return 1; ``` * 根據題目提示:passing zero to clz(), which is not a valid argument ```cpp #5 int mask = (1 << ( 32 - __builtin_clz(N) )) -1; #6 return ~N & mask; ``` > 以 8 bits 為例 > `N` = 5 = ( ==0000 0==101 )~2~ * 依據題目要求,應該會需要 `~N`= ~5 = ( ==1111 1==010 )~2~ `// 1s' complement` * 黃色標記的部份應該要為 0 * 需要更改的 bits 數 = m = `__builtin_clz(N)` = 5 * 因此,我們還需要一個 `mask` = ( 0000 0111 )~2~ = ( 0000 1000 ) - 1 = ( 1 << 3 ) - 1 = ( 1 << ( 8 - m ) ) - 1 * 綜合上述,並推廣到 32 bits,可得: * `mask = (1 << ( 32 - __builtin_clz(N) )) - 1` * 求解即為 `~N & mask` * [效能分析](https://leetcode.com/submissions/detail/403020096/): ![](https://i.imgur.com/hgHc2U9.png) #### LeetCode [41. First Missing Positive](https://leetcode.com/problems/first-missing-positive/) ( 利用 clz ) * Given an unsorted integer array, find the smallest missing positive integer. * Your algorithm should run in O(n) time and uses constant extra space. ##### 失敗版 ```cpp= int firstMissingPositive(int* nums, int numsSize){ int note = 0; for (int i = 0; i < numsSize ; i++){ if (nums[i] > 0 && nums[i] <= numsSize){ note |= (1 << (nums[i] -1)); } } if (note==0) return 1; for(int j = 0 ; j < (33 - __builtin_clz(note)); j++){ if (!((note >> j) & 0x1)) return j+1; } return 0; } ``` * 利用 `note` 的各 bit 紀錄,有哪些正整數出現過 * 若 `nums` 中有 4,則 `note |= (0000 1000)` * 最後利用 `!((note >> j) & 0x1)` 找出最早出現 0 的位置,即為所求 :::danger runtime error: shift exponent 53 is too large for 32-bit type 'int' ::: * 顯然 bit 數根本不夠用 ##### 成功版 ```cpp= #define XORSWAP(a, b) \ ((a) ^= (b), (b) ^= (a),(a) ^= (b)) \ int firstMissingPositive(int* nums, int numsSize){ for(int i=0;i<numsSize;i++){ if(nums[i] > 0 && nums[i] < numsSize){ int pos = nums[i] - 1; if(pos != i && nums[i] != nums[pos]){ XORSWAP(nums[i],nums[pos]); i--; } } } for(int i = 0;i < numsSize;i++){ if(nums[i] != i + 1) return i + 1; } return numsSize + 1; } ``` >Input: [1,2,0] Output: 3 >Input: [3,4,-1,1] Output: 2 >Input: [7,8,9,11,12] Output: 1 * 在 [Q5](#Q5-bit-array-%E4%B9%9F%E7%A8%B1-bitset-%E5%9C%A8%E5%BD%B1%E5%83%8F%E8%99%95%E7%90%86%E7%9A%84%E4%BD%BF%E7%94%A8) 中 ,**陣列 `out`** 有 記錄 bit 為 1 所在位置 的功能 * 在這題,我們讓 陣列`num` 也擁有 ==記錄== 的功能 * 第一次走訪的過程中 ( `#6`~`#14` ) * 若 0 < `num[i]` < `numsSize`,則將 `num[i]` 和 `num[num[i]-1]` 互換 * 前提:**位置不同** 且 **內容不同** ( i.e.`(pos != i && nums[i] != nums[pos])`) * `#11`: 交換完之後,`num[i]` 還要在檢查一次 ( 因為內容有更動 ) * 第二次走訪 ( `#16`~`#19` ) * 若 位置編號 和 內容 對不上 ( i.e. `nums[i] != i + 1` ),代表應該出現的內容為 Missing Positive * 對得上的情形:`num[0] == 1`、 `num[1] == 2` etc. * 第一個 沒出現的正確數值,即為 First Missing Positive #### 研讀 [2017 年修課學生報告](https://hackmd.io/@3xOSPTI6QMGdj6jgMMe08w/Bk-uxCYxz),理解 clz 的實作方式,並舉出 Exponential Golomb coding 的案例說明 ## [Q3](https://hackmd.io/@sysprog/2020-quiz3#%E6%B8%AC%E9%A9%97-3). 利用 `__builtin_popcount` 來實作 [LeetCode 1342. Number of Steps to Reduce a Number to Zero](https://leetcode.com/problems/number-of-steps-to-reduce-a-number-to-zero/) * Given a non-negative integer num, return the number of steps to reduce it to zero. * If the current number is even, you have to divide it by 2, * otherwise, you have to subtract 1 from it. * 假設 `int` 為 32 位元寬度 ```c= int numberOfSteps (int num) { return num ? AAA + 31 - BBB : 0; // AAA = __builtin_popcount(num) // BBB = __builtin_clz(num) } ``` :::info * [population count](https://en.wikichip.org/wiki/population_count) 簡稱 popcount 或叫 sideways sum, * **計算數值的二進位表示中,有多少位元是 1** * 在一些場合下很有用,例如計算 0-1 稀疏矩陣 (sparse matrix)或 bit array 中非 0 元素個數、計算兩個字串的 Hamming distance。 * GCC 提供對應的內建函式: **`__builtin_popcount(x)`** ::: >以 14 為例: >> 1. 14 = ( 0000 1110 ) is even; divide by 2 and obtain 7. >> 3. 7 = ( 0000 0111 ) is odd; subtract 1 and obtain 6. >> 4. 6 = ( 0000 0110 ) is even; divide by 2 and obtain 3. >> 5. 3 = ( 0000 0011 ) is odd; subtract 1 and obtain 2. >> 6. 2 = ( 0000 0010 ) is even; divide by 2 and obtain 1. >> 7. 1 = ( 0000 0001 ) is odd; subtract 1 and obtain 0. * 由上述例子,可以有以下推論: * **1 所在的最高位元是第 4 個bit**,代表需要 divide by 2 ( i.e. 右移 ) ==3 次== * 最高位元的 1 只須 subtract 1 ,不用 divide by 2 * **總共有 3 個 1** ,代表需要 subtract 1 ==3次== * 上面兩項加總,3 + 3 = ==6== 即為所求 * 更廣泛的推論: * **1 所在的最高位元是第 m 個bit**,代表需要 divide by 2 ( i.e. 右移 ) ==( m - 1 ) 次== * 最高位元的 1 只須 subtract 1 ,不用 divide by 2 * **總共有 k 個 1** ,代表需要 subtract 1 ==k次== * 上面兩項加總,==( m - 1 ) + k== 即為所求 * 程式碼: * ( m - 1 ) = ( 32 - `__builtin_clz(num)` ) - 1 * k = `__builtin_popcount(num)` * ( m - 1 ) + k = [ ( 32 - `__builtin_clz(num)` ) - 1 ] + `__builtin_popcount(num)` = `__builtin_popcount(num)` + 31 - `__builtin_clz(num)` * 故 **AAA = `builtin_popcount(num)`**、**BBB = `builtin_clz(num)`** ## Q3. 進階題 #### 改寫程式碼 ( 利用 [Bit Twiddling Hacks](http://graphics.stanford.edu/~seander/bithacks.html) ) >提示: Bit Twiddling Hacks 中提及許多 bitwise operation 的使用,如 bit inverse、abs 等等,是極好的參考材料。 #### 改寫程式碼 ( 避免用到編譯器的擴充功能,只用 C99 特徵及 bitwise 運算 ) ## Q4. 64-bit GCD (greatest common divisor, 最大公因數) * [Greatest Common Divisor 特性和實作考量](https://hackmd.io/@sysprog/gcd-impl) #### 有使用 `%` 的版本 ```c #include <stdint.h> __uint64_t gcd64(__uint64_t u, __uint64_t v) { /* case 1 : gcd(a, 0) = a */ if (!u || !v) return u | v; while (v) { __uint64_t t = v; v = u % v; u = t; } return u; } ``` - $\gcd(a, 0) = |a|$ - $\gcd(a, b) = \gcd(b, a)$ - $\gcd(a, b) = \gcd(a, b \% a)$ #### 不使用 `%` 的版本 * [Binary GCD](https://en.wikipedia.org/wiki/Binary_GCD_algorithm) > [Binary GCD Algorithm](https://iq.opengenus.org/binary-gcd-algorithm/) ```c= #include <stdint.h> __uint64_t gcd64(__uint64_t u, __uint64_t v) { /* case 1 : gcd(a, 0) = a */ if (!u || !v) return u | v; int shift; for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } while (!(u & 1)) u /= 2; do { while (!(v & 1)) v /= 2; if (u < v) { v -= u; } else { __uint64_t t = u - v; u = v; v = t; } } while (XXX); // XXX = v return YYY; // YYY = u << shift } ``` * 程式碼解析: ```cpp #6 int shift; #7 for (shift = 0; !((u | v) & 1); shift++) { #8 u /= 2, v /= 2; #9 } ``` * binary gcd 的精神就是當兩數為偶數時,必有一 $2$ 因子。 $$ \text{gcd}(u, v) = 2·\text{gcd}(\frac{u}{2}, \frac{v}{2}) $$ * `shift` 紀錄有幾個 $2$ 因子 ```cpp #10 while (!(u & 1)) #11 u /= 2; ``` * 且一數為奇數另一數為偶數,則無 $2$ 因子。 $$ \text{gcd}(u, v) = \text{gcd}(u, \frac{v}{2}) $$ ```cpp #12 do { #13 while (!(v & 1)) #14 v /= 2; #15 if (u < v) { #16 v -= u; #17 } else { #18 __uint64_t t = u - v; #19 u = v; #20 v = t; #21 } #22 } while (XXX); ``` * 假設 $u >= v$ ,總結一些定理: | $u$ | $v$ | $gcd(u, v)$ | |:---------:|:---------:|:----------------------------------:| | $u$ | $0$ | $u$ | | $even$ | $even$ | $2*gcd( \frac{u}{2},\frac{v}{2} )$ | | $even$ | $odd$ | $gcd( \frac{u}{2},v )$ | | $odd$ | $even$ | $gcd( u,\frac{v}{2} )$ | | ==$odd$== | ==$odd$== | ==$gcd( \frac{u-v}{2},v )$== | * `#12`~`#22` 便是不斷操作 $gcd(u, v)=gcd( \frac{u-v}{2},v )$ 的部份,直到 `v` = 0 為止 * 因此, **XXX = `v`** ```cpp #23 return YYY; ``` * `#12`~`#22` 結束後,`v` = 0 * $gcd(u, 0)=u$ ,因此 **YYY = `u << shift`** * 別忘記 `#6`~`#9` 得到 `shift` 個 2 因子 ## Q4. 進階題 #### 在 x86_64 上透過 `__builtin_ctz` 改寫 GCD ```c= #include <stdint.h> #define min(x, y) \ ((x) < (y) ? (x) : (y)) \ __uint64_t gcd64(__uint64_t u, __uint64_t v) { /* case 1 : gcd(a, 0) = a */ if (!u || !v) return u | v; int shift = min(__builtin_ctz(u), __builtin_ctz(v));; u >>= shift; v >>= shift; while (!(u & 1)) u >>= 1; do { while (!(v & 1)) v >>= 1; if (u < v) { v -= u; } else { __uint64_t t = u - v; u = v; v = t; } } while (v); return u << shift ; } ``` * 我們可以改良為: ( `#10`~`#12` ) $$ \text{even_factor} = \text{min}(\text{ctz}(x), \text{ctz}(y))$$ $$ \text{gcd}(x, y) = \text{even_factor}·\text{gcd}((x\gg \text{even_factor}), (y\gg \text{even_factor}))$$ #### 分析對效能的提升 ## Q5. [bit array](https://en.wikipedia.org/wiki/Bit_array) (也稱 bitset) 在影像處理的使用 #### 考慮以下程式碼 : ```cpp= #include <stddef.h> size_t naive(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; for (size_t k = 0; k < bitmapsize; ++k) { uint64_t bitset = bitmap[k]; size_t p = k * 64; for (int i = 0; i < 64; i++) { if ((bitset >> i) & 0x1) out[pos++] = p + i; } } return pos; } ``` * 程式碼解析: * 由 `uint64_t *bitmap`、`bitmap[k]`,可以判斷 bitmap 為 uint64_t Array * 由 `uint32_t *out`、`out[pos++]`,可以判斷 out 為 uint32_t Array :::success $§\space 6.4.4.4\space Character \space constants \space$( p. 61 ) * A wide character constant has type `wchar_t`, an integer type defined in the `<stddef.h>` header. The value of a wide character constant containing a single multibyte character that maps to a member of the extended execution character set is the wide character corresponding to that multibyte character, as defined by the `mbtowc` function, with an implementation-defined current locale. ==The value of a wide character constant containing more than one multibyte character, or containing a multibyte character or escape sequence not represented in the extended execution character set, is implementation-defined.== ::: ```cpp #04 size_t pos = 0; #05 for (size_t k = 0; k < bitmapsize; ++k) { #06 uint64_t bitset = bitmap[k]; ``` * 每 64 個 bits 一組進行迴圈 ```cpp #07 size_t p = k * 64; #08 for (int i = 0; i < 64; i++) { #09 if ((bitset >> i) & 0x1) #10 out[pos++] = p + i; // out[pos] = p + i; // pos +=1 ; #11 } ``` * `#09` : 逐一檢查 `bitset` 的 64 個 bits ,只要發現該 bit 為 1 (i.e. `((bitset >> i) & 0x1)` 為 true ),便進入 `#10` * `#10` : 將 `p + i` 寫入 `out[pos++]` * `#07` : `bitset` 的第 1 個 bit = `bitmap` 中的第 `p` 個 bit * 1 出現在第`p + i`個 bit 中 ( 以 `bitmap` 的角度來看 ) * 將 出現 1 的編號 ( i.e. `p + i` ) 紀錄於 陣列`out` * 可推理出 ==`pos` 代表 `bitmap` 中,出現 1 的個數== > array 由 0 起算;人類 由 1 起算 > 因此,需要 `pos++` #### `__builtin_ctzll` 改進版 : ```cpp= #include <stddef.h> size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; uint64_t bitset; for (size_t k = 0; k < bitmapsize; ++k) { bitset = bitmap[k]; while (bitset != 0) { uint64_t t = KKK; // KKK = bitset & -bitset int r = __builtin_ctzll(bitset); out[pos++] = k * 64 + r; bitset ^= t; } } return pos; } ``` :::info [GCC 提供對應的內建函式](http://sunmoon-template.blogspot.com/2017/04/gcc-built-in-functions-for-binary-gcc.html): * **`builtin_ctzll (unsigned long long)`** * 返回右起第一個 1 之後的 0 的個數 ::: * 程式碼解析: ```cpp #08 while (bitset != 0){ ``` * 第一個改良:若 `bitset` 之中沒有 1 ( i.e. 全為 0 ),則不用進行檢查,可直接進入下一組 ```cpp #10 int r = __builtin_ctzll(bitset); #11 out[pos++] = k * 64 + r; ``` * `#10`、`#11` 可將 `bitmap` 中出現 1 的 bit 所在 index 紀錄於陣列 `out` 中 ```cpp #09 uint64_t t = KKK; #12 bitset ^= t; ``` * 觀察程式碼,我們猜測 `#12` 可將 ==`bitset` 中 最低位的 1 換成 0==,如此一來,下個 while 迴圈才能正確執行 > 以 8 bits 為例 * 假設 `bitset` = ( **** *==1==00 ) * `t` 應為 ( 0000 0==1==00 ) * `-bitset` = ( $\bar*$$\bar*$$\bar*$$\bar*$ $\bar*$ ==1==00 ) `// 2's complement` * 故,`t` = **KKK = `bitset & -bitset`** ## Q5. 進階題 #### 舉出 Q5 用在哪些真實案例中 #### 檢驗 clz 改寫的程式碼相較原本的實作有多少改進 >應考慮到不同的 [bitmap density](http://www.vki.com/2013/Support/docs/vgltools-3.html) #### 思考進一步的改進空間