# 2020q3 Homework4 (quiz4) contributed by < `ptzling310` > > [2020q3 第 4 週測驗題](https://hackmd.io/@sysprog/2020-quiz4) ## 測驗 `1`: LeetCode [461. Hamming Distance](https://leetcode.com/problems/hamming-distance/) ```c int hammingDistance(int x, int y) { return __builtin_popcount(x OP y); } ``` - [x] OP = `^` ## 理解 ![](https://i.imgur.com/q9YzhCs.png) 觀察: Distance( (0100)~2~ , (1001)~2~ ) = 3 Distance( (0110)~2~ , (1110)~2~ ) = 1 恰為兩數取 `XOR` 後, bit 1 的個數。 故 OP 為 `^`。 ### 不使用 gcc extension 的 C99 實作程式碼 :::spoiler [詳解二進位數中 1 的個數](http://github.tiankonguse.com/blog/2014/11/16/bit-count-more.html) ``` 101100 (44) 101011 (44-1) ------------------ 44 與 43 的後 3 位相反 101100 (44) & 101011 (43) ------------------- 101000 (40) 比原本的 44 相比,少了最右邊的 bit 1 101000 (40) 100111 (40-1) ------------------- 40 與 39 的後 4 位相反 101000 (40) & 100111 (40-1) ------------------- 100000 (32) 比原本的 40 相比,少了最右邊的 bit 1 100000 (32) 011111 (31) ------------------ 32 與 31 的後 6 位相反 100000 (32) & 011111 (31) ------------------ 000000 (0) ∴ bit 1 的個數與上述運算直至 0 的次數相等,可利用此技巧來計算二進制中 bit 1 的個數。 ``` ::: ```c int hammingDistance(int x, int y){ int dist = x ^ y; int n = 0 ; while(dist && (++n , dist&=dist-1)); return n; } ``` 參考[詳解二進位數中 1 的個數](http://github.tiankonguse.com/blog/2014/11/16/bit-count-more.html)的方式,將 `x^y` 不斷的做 `-1` 再取 `&` 直到求出結果為 `0`,最終將計算次數記錄到變數 `n` 當中,則 `n` 就是 `x^y` 的 bit 1 個數了。 ![](https://i.imgur.com/IhmvOxc.jpg) ### [477. Total Hamming Distance](https://leetcode.com/problems/total-hamming-distance/) :::spoiler Runtime: Time Limit Exceeded ```c int hammingDistance(int x, int y) { return __builtin_popcount(x ^ y); } int totalHammingDistance(int* nums, int numsSize){ if(nums == NULL || numsSize == 0) return 0; int result=0; for (int i=0; i < numsSize ; i++){ for (int j= i+1 ; j < numsSize ; j++){ result += hammingDistance(nums[i],nums[j]); } } return result; } ``` ![](https://i.imgur.com/ldL3V9V.jpg) 根據網頁上的 explain ,一次取兩個數去做 `hammingDistance`,但這樣會超時。 ::: ``` Input: 4, 14, 2 Output: 6 c1 c2 c3 c4 4 = ( 0 1 0 0 ) 14= ( 1 1 1 0 ) 2 = ( 0 0 1 0 ) ``` 觀察每一個 column: ``` c4: 皆為 0 ,因為概念還是要用 `XOR` , 所以若沒有相異的 bit ,並不會增加 total distance。 c3:針對有 bit 1 出現的 14 以及 2。會個別與 4 的 bit 0 做 `XOR` 會增加 total distance。故 c3 應該會產生 2 的距離。 c2:同 c3 的情況,產生 2 的距離。 c1:只有 14 有 bit 1,分別與 4 以及 2 的 bit 0 做 `XOR` 可增加 total distance 2。 ``` 綜合上述, total distance 應為 `2 + 2 + 2` = 6。 再來,探討如何達到上述討論的運算,如果我們藉由一次看一個去,去和其他數比較,那麼也會有超時的問題。所以在本題的 dicussion 中,討論出如何達到計算出每個 column 貢獻的距離。 藉由計算==每個 column 的 bit 0 以及 bit 1 的個數,再將兩數相乘==即得每個 column 所貢獻的距離,再加總即得 total hamming distance。 ```c int totalHammingDistance(int* nums, int numsSize) { if(nums == NULL || numsSize == 0) return 0; int result = 0; //紀錄最後的distance for(int i = 0 ; i <31 ; i++){ int count_one = 0; for(int j = 0 ; j < numsSize ; j++){ if(nums[j] & (1<<i)) count_one++;//計算bit 1 } result += count_one * (numsSize-count_one); } return result; } ``` ![](https://i.imgur.com/3DsOfkp.jpg) :::warning TODO:時間還可以再改進。 因為這個版本仍是一個 bit 一個 bit 的檢查, 不過目標是再看 bit 1 ,那就聯想到 gcc extension! ::: ------------------------------------ ## 測驗 `2`: LeetCode [1483. Kth Ancestor of a Tree Node](https://leetcode.com/problems/kth-ancestor-of-a-tree-node/) >給你一棵樹,樹上有 n 個節點,編號自 0 到 n−1。樹以父節點陣列的形式提供,其中 parent[i] 是節點 i 的父節點。樹的根節點是編號為 0 的節點。請設計 treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k) 函式,回傳節點 node 的第 k 個祖先節點。若不存在這樣的祖先節點,回傳 -1。樹節點的第 k 個祖先節點是從該節點到根節點路徑上的第 k 個節點。 ![](https://assets.leetcode.com/uploads/2019/08/28/1528_ex1.png) ``` Input: ["TreeAncestor","getKthAncestor","getKthAncestor","getKthAncestor"] [[7,[-1,0,0,1,1,2,2]],[3,1],[5,2],[6,3]] Output: [null,1,0,-1] ``` :::spoiler 完整程式 ```c= #include <stdlib.h> typedef struct { int **parent; int max_level; } TreeAncestor; TreeAncestor *treeAncestorCreate(int n, int *parent, int parentSize) { TreeAncestor *obj = malloc(sizeof(TreeAncestor)); obj->parent = malloc(n * sizeof(int *)); int max_level = 32 - __builtin_clz(n) + 1; for (int i = 0; i < n; i++) { obj->parent[i] = malloc(max_level * sizeof(int)); for (int j = 0; j < max_level; j++) obj->parent[i][j] = -1; } for (int i = 0; i < parentSize; i++) obj->parent[i][0] = parent[i]; for (int j = 1;; j++) { int quit = 1; for (int i = 0; i < parentSize; i++) { obj->parent[i][j] = obj->parent[i][j -1] == -1 ? -1 : obj->parent[obj->parent[i][j - 1]][j - 1]; if (obj->parent[i][j] != -1) quit = 0; } if (quit) break; } obj->max_level = max_level - 1; return obj; } int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k) { int max_level = obj->max_level; for (int i = 0; i < max_level && node != -1; ++i) if (k & (1<<i)) node = obj->parent[node][i]; return node; } void treeAncestorFree(TreeAncestor *obj) { for (int i = 0; i < obj->n; i++) free(obj->parent[i]); free(obj->parent); free(obj); } ``` ::: ### 理解 #### Struct ```c typedef struct { int **parent; int max_level; } TreeAncestor; ``` 在 `*treeAncestorCreate` 中: ```c TreeAncestor *obj = malloc(sizeof(TreeAncestor)); obj->parent = malloc(n * sizeof(int *)); ``` 觀察 `obj->parent` ,這是分配 n 個 指標空間,所以 `parent` 指向的是指標。 所以 AAA = `int **parent`。 #### treeAncestorCreate ```c=13 int max_level = 32 - __builtin_clz(n) + 1; ``` 可以計算出 n 個 node 下, 一個 Binary Tree 有幾 level。若 n = 7 =(0...0111)~2~ ,則 max_level = 32 - 29 + 1 = 4。 n = 13 = (0..1101)~2~,則 max_level = 32 - 28 + 1 = 5。 :::spoiler ~~節點數(n) 與 level 的關係~~ 觀察 binary tree 的 結構。 | Level | 最大 Node 數 (n) | | | -------- | -------- | -------- | | 1 | 1 | $2^1-1$ | | 2 | 3 | $2^2-1$ | | ...|...|... | l | | $2^l-1$| 所以 level 為 l 之 binary tree 最多會有 $2^n-1$ 個 nodes。當我們知道 n 個點時,就可以反推 level ! $2^l-1=n$ $2^l=n+1$ $l= \lceil log_2(n+1)\rceil= log_2(n)+1$ 但為何$log_2(n)+1$ = 32 - __builtin_clz(n)? 計算 $log_2(n)$ 也可以利用位移操作, ``` Log2(x) = result; while (x >>= 1) result++; ``` `*` level 從 `1` 開始計算。 所以本程式就是這兩行程式的改寫。 為何刪除? 原本認為 `parent[i][j]` 的 j 應為階層數, 也就是 `parent[i][j]` 即是 node i 的第 j 代祖先。 但在後續解釋不通。故刪除此推測。 ::: ```c=14 for (int i = 0; i < n; i++) { obj->parent[i] = malloc(max_level * sizeof(int)); for (int j = 0; j < max_level; j++) obj->parent[i][j] = -1; } ``` 產生二維陣列 `parent[n][max-level]`。 將陣列的內容都預設為 `-1`。 | [i] \ [j] | 0 | 1 | 2 | 3 | | -------- | -------- | -------- |-------- |-------- | | 0 | -1 | -1 | -1 | -1 | | 1 | -1 | -1 | -1 | -1 | | 2 | -1 | -1 | -1 | -1 | | 3 | -1 | -1 | -1 | -1 | | 4 | -1 | -1 | -1 | -1 | | 5 | -1 | -1 | -1 | -1 | | 6 | -1 | -1 | -1 | -1 | ```c=19 for (int i = 0; i < parentSize; i++) obj->parent[i][0] = parent[i]; ``` 將 input 的 [-1,0,0,1,1,2,2] 填入, 所以 `parent[i][0]` 會記錄節點 i 的父點(前一代) | [i] \ [j] | 0 | 1 | 2 | 3 | | -------- | -------- | -------- |-------- |-------- | | 0 | -1 | -1 | -1 | -1 | | 1 | 0 | -1 | -1 | -1 | | 2 | 0 | -1 | -1 | -1 | | 3 | 1 | -1 | -1 | -1 | | 4 | 1 | -1 | -1 | -1 | | 5 | 2 | -1 | -1 | -1 | | 6 | 2 | -1 | -1 | -1 | ```c=22 for (int j = 1;; j++) { int quit = 1; for (int i = 0; i < parentSize; i++) { obj->parent[i][j] = obj->parent[i][j + BBB] == -1 ? -1 : obj->parent[obj->parent[i][j - 1]][j - 1]; if (obj->parent[i][j] != -1) quit = 0; } if (quit) break; } ``` `parent[i][j]` 是紀錄 node i 的第前 $2^{j}$ 代祖先。 `j` 從 1 開始,所以第一次迴圈會到 `parent[0][1]` 。 `obj->parent[i][j + BBB] == -1` 要討論 i 的第前 $2^j$ 代時,先檢查 i 的第前 $2^{j-1}$ 代是否存在。 ``` 假設目前 i = 0, j = 2,想知道 node 0 的第前 2 代, 先檢查 node 0 的第前 1 代是否存在 (查表可知 parent[0][1] = -1 所以不存在), 這樣也沒必要重新設定 node 0 的第前 2 代了,維持原來的值 -1 即可。 ``` 故 BBB = `-1`。 :::spoiler Trace - 在 `parent[0][1]` ,由於`obj->parent[0][0] = -1` ,故不修改 `parent[0][1]` 之值(維持 -1)。接著 quit 不符合 `if` 要求,故值維持 1 ,執行 `break`。結束 node 0 的設定。 - 在 `parent[1][1]` ,由於`obj->parent[1][0]` = 0 ≠ -1 ,所以要修改 `parent[1][1]` 的值。 `obj->parent[ obj->parent[1][0] ][0]` = `obj->parent[0][0]` = -1 。在進入 `parent[1][2]` 時,會因為`obj->parent[1][1]` = -1,而停止對 node 1 的設定。 - `parent[3][1]` 要修改值為: `obj->parent[obj->parent[3][0]][0]` = `obj->parent[1][0]` = 0 - `parent[3][2]` 要修改值為: `obj->parent[obj->parent[3][1]][1]` = `obj->parent[0][0]` = -1 - `parent[5][1]` 要修改值為: `obj->parent[obj->parent[5][0]][0]` = `obj->parent[2][0]` = 0 - `parent[5][2]` 要修改值為: `obj->parent[obj->parent[5][1]][1]` = `obj->parent[0][0]` = -1 ::: | [i] \ [j] | 0 | 1 | 2 | 3 | | -------- | -------- | -------- |-------- |-------- | | 0 | -1 | -1 | -1 | -1 | | 1 | 0 | -1 | -1 | -1 | | 2 | 0 | -1 | -1 | -1 | | 3 | 1 | 0 | -1 | -1 | | 4 | 1 | 0 | -1 | -1 | | 5 | 2 | 0 | -1 | -1 | | 6 | 2 | 0 | -1 | -1 | 先前推測 `parent[i][j]` 是紀錄 node i 的第前 j 代祖先, 觀察此表,應為:`parent[i][j]` 是紀錄 node i 的第前 $2^{j}$ 代祖先 所以 `parent[1][2]` 是 node 1 的 第前 $2^2=4$ 代祖先, 不存在,故為 -1 。 ```c=32 obj->max_level = max_level - 1; ``` 如果不進行加一的話呢?則在某些狀況下會無法在 table 中產生全為 `-1` 的 colume。 (參考 [`Rsysz`的共筆](https://hackmd.io/@Rsysz/quiz4#%E5%BB%B6%E4%BC%B8%E5%95%8F%E9%A1%8C1),有舉出會產生錯誤的例子! ) #### treeAncestorGetKthAncestor ```c=40 if (k & ( 1 << i )) node = obj->parent[node][i]; ``` 由於是 `return node`, 故這裡的 `node` 就是我們所要回傳的祖先的編號。 代表應該是藉由此段程式碼移動所要回傳的 node 為止。 ``` 這裡利用 n = 14 的 full binary tree 來舉例討論。 0 / \ 1 2 / \ / \ 3 4 5 6 / \ / \ / \ / \ 7 8 9 10 11 12 13 14 ``` 當想要知道 node 7 的第 2 代祖先 (應為 1) | k | i | 1<<i | k&(1<<i) | node = parent[node][i] | | -------- | -------- | -------- |-------- |-------- | | 2 = (0010)~2~ | 0 | (0001)~2~ | (0000)~2~ |不進入 if | | | 1 | (0010)~2~ | (0010)~2~ |node = parent[7][1]=1 | 現在想要知道 node 7 的第 3 代祖先 (應為 0) | k | i | 1<<i | k&(1<<i) | node = parent[node][i] | | -------- | -------- | -------- |-------- |-------- | | 3 = (0011)~2~ | 0 | (0001)~2~ | (0001)~2~ | node = parent[7][0]=3| | | 1 | (0010)~2~ | (0010)~2~ | node = parent[3][1]=0| 由於在 table 中是紀錄 i 的第 $2^j$,若所欲存取的第 k 代祖先為二的冪,則 k 的二進制中必定只有一個 bit 1 出現,則我們的 node 只會移動一次。 若 k 不為二的冪,則依照 bit 1 的數量移動,如上例 k = 3 = (0011)~2~,我們就需要先讀走到 node 3,變成讀取 node 3 的第 $2^1 = 2$ 代祖先。 #### treeAncestorFree ```c=47 for (int i = 0; i < obj->n; i++) free(obj->parent[i]); free(obj->parent); free(obj); ``` 在 `treeAncestorFree` 中沒有定義到 `n` 為何。可以看的出來 n 就是紀錄節點數,由於這裡是使用 `obj->n` 來做判斷,故在 struct 中,必須加上 `n`。 這樣程式才能夠執行。 - `struct` 的修正 ```diff typedef struct { int **parent; int max_level; + int n; } TreeAncestor; ``` - `treeAncestorCreate` 的修正 ```diff TreeAncestor* treeAncestorCreate(int n, int* parent, int parentSize) { TreeAncestor *obj = malloc(sizeof(TreeAncestor)); + obj-> n = n; obj->parent = malloc(n * sizeof(int *)); ... } ``` ------------- ## 測驗 `3`:[FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz) >從 1 數到 n,如果是 3的倍數,印出 “Fizz” 如果是 5 的倍數,印出 “Buzz” 如果是 15 的倍數,印出 “FizzBuzz” 如果都不是,就印出數字本身 ``` n :1 2 3 4 5 6 7 ... 14 15 1 2 Fizz 4 Buzz Fizz 7 ... 14 FizzBuzz .... ``` ```c= #define MSG_LEN 8 static inline bool is_divisible(uint32_t n, uint64_t M) { return n * M <= M - 1; } static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1; static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1; int main(int argc, char *argv[]) { for (size_t i = 1; i <= 100; i++) { uint8_t div3 = is_divisible(i, M3); uint8_t div5 = is_divisible(i, M5); unsigned int length = (2 << div3) << div5; char fmt[MSG_LEN + 1]; strncpy(fmt, &"FizzBuzz%u"[(MSG_LEN >> KK1) >> (KK2 << KK3)], length); fmt[length] = '\0'; printf(fmt, i); printf("\n"); } return 0; } ``` - [x] KK1 = `div5` - [x] KK2 = `div3` - [x] KK3 = `2` #### 觀念 ``` string literal: "fizzbuzz%u" offset: 0 4 8 當為 3 的倍數:印 [0~3] 當為 5 的被數:印 [4~7] 當為 15 的倍數:印 [0~7] 其他:印 [8] ``` ### 理解 ```c=3 static inline bool is_divisible(uint32_t n, uint64_t M) { return n * M <= M - 1; } static uint64_t M3 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 3 + 1; static uint64_t M5 = UINT64_C(0xFFFFFFFFFFFFFFFF) / 5 + 1; ``` 參照[判斷某個數值能否被指定的除數所整除](https://hackmd.io/159Bz7qOSPaPUOSPg3ie0A#%E6%B8%AC%E9%A9%97-4%EF%BC%9A-%E5%88%A4%E6%96%B7%E6%9F%90%E5%80%8B%E6%95%B8%E5%80%BC%E8%83%BD%E5%90%A6%E8%A2%AB%E6%8C%87%E5%AE%9A%E7%9A%84%E9%99%A4%E6%95%B8%E6%89%80%E6%95%B4%E9%99%A4)。 ```c=12 uint8_t div3 = is_divisible(i, M3); uint8_t div5 = is_divisible(i, M5); unsigned int length = (2 << div3) << div5; ``` 觀察: | i | div3 | div5 | print|start|length| | -------- | -------- | -------- | -------- | -------- |-------- | | i % 3 == 0 | 1 | 0 | Fizz|0|4| | i % 5 == 0 | 0 | 1 | Buzz|4|4| | i % 15 == 0 | 1 | 1 |FizzBuzz|0|8| | 其他 | 0 | 0 |i|8|2| `length` 在 `div3 = 0` 即 `div5 = 0` 的情況下,會為 2 = (0..010)~2~; 若 `div3 = 1` 或 `div5 = 1 ` 的情況下,會為 `2 << 1` = (0...0100)~2~ = 4; 在 `div3 = 1` 且 `div5 = 1 ` 的情況下,會為 `(2 << 1) << 1` = (0..01000)~2~ = 8。 故藉此法,可控制 `length` 為何。 ```c=16 char fmt[MSG_LEN + 1]; //char fmt[9]; strncpy(fmt, &"FizzBuzz%u"[(MSG_LEN >> div3) >> (div5 << 2)], length); ``` 建立一個陣列: `fmt`,可存9個 char。 `struncpy(目的,來源,長度)`,由於 `"FizzBuzz%u"` 並不是要從頭複製,所以後面要再去設定起始位置。`[(MSG_LEN >> KK1) >> (KK2 << KK3)]` 即是上表的 `start`,KK1、KK2應該與`div3`、`div5` 有關。 - 採 KK1 = `div3` , KK2 = `div5`。`MSG_LEN` = 8 = (1000)~2~ - 若 KK1 = `div3` = 1;KK2 = `div5` = 0 ``` (MSG_LEN >> KK1) : (1000) >> 1 = (0100) (KK2 << KK3) : (0000) << KK3 = (0000) (MSG_LEN >> KK1) >> (KK2 << KK3) : (0100) >> (0000) = (0100) = 4 ``` 但目標 `start` 應該為 `0`,故此假設是錯的。 - 採 KK1 = `div5` , KK2 = `div3`。`MSG_LEN` = 8 = (1000)~2~ - 若 KK1 = `div5` = 0;KK2 = `div3` = 1 ``` (MSG_LEN >> KK1) : (1000) >> 0 = (1000) (KK2 << KK3) : (0001) << KK3 (MSG_LEN >> KK1) >> (KK2 << KK3) : (1000) >> ( (0001) << KK3 ) = 0 ``` 為了達到 `start` = 0,利用 KK3 來進行修正,(1000)~2~ 還需右移 4 才會變 0, 所以 `( (0001) << KK3 )` = 4 , KK3 = `2`。 - 若 KK1 = `div5` = 1;KK2 = `div3` = 0 ``` (MSG_LEN >> KK1) : (1000) >> 1 = (0100) (KK2 << KK3) : (0000) << KK3 = (0000) (MSG_LEN >> KK1) >> (KK2 << KK3) : (0100) << 0 = 4 ``` 這裡 KK3 不論是多少都不會有作用,不過結果是對的。 - 若 KK1 = `div5` = 1;KK2 = `div3` = 1 ``` (MSG_LEN >> KK1) : (1000) >> 1 = (0100) (KK2 << KK3) : (0001) << 2 = (0100) (MSG_LEN >> KK1) >> (KK2 << KK3) : (0100) >> (0100) = 0 ``` - 若 KK1 = `div5` = 0;KK2 = `div3` = 0 ``` (MSG_LEN >> KK1) : (1000) >> (0000) = (1000) (KK2 << KK3) : (0000) >> 2 = (0000) (MSG_LEN >> KK1) >> (KK2 << KK3) : (1000) >> (0000) = (1000) ``` 結果正確,所以KK1 = `div5` , KK2 = `div3` , KK3 = `2`。 ```c=18 fmt[length] = '\0'; ``` 補上結尾符號。 ### 評估 naive.c 和 bitwise.c 效能落差 這裡設定 n = 100,000。 #### naive.c `$ sudo perf stat ./naive` ``` Performance counter stats for './naive': 229.68 msec task-clock # 0.110 CPUs utilized 10 context-switches # 0.044 K/sec 0 cpu-migrations # 0.000 K/sec 53 page-faults # 0.231 K/sec 6,2726,9708 cycles # 2.731 GHz 4,6991,1124 instructions # 0.75 insn per cycle 9389,8810 branches # 408.828 M/sec 126,5634 branch-misses # 1.35% of all branches 2.081604900 seconds time elapsed 0.044511000 seconds user 0.186140000 seconds sys ``` #### bitwise.c `$ sudo perf stat ./bitwise` ``` Performance counter stats for './bitwise': 342.36 msec task-clock # 0.088 CPUs utilized 7 context-switches # 0.020 K/sec 0 cpu-migrations # 0.000 K/sec 52 page-faults # 0.152 K/sec 12,1452,9781 cycles # 3.548 GHz 8,7415,3716 instructions # 0.72 insn per cycle 1,7315,6927 branches # 505.778 M/sec 257,6487 branch-misses # 1.49% of all branches 3.874382673 seconds time elapsed 0.075787000 seconds user 0.267251000 seconds sys ``` 藉由測驗所提供的程式,在 n = 100,000 的情況下,`navie.c` 的執行時間較短。 ------------------------------------------- ## 測驗 `4`:PAGE_QUEUES 用最少的 case-break 敘述做出同等 `size_t blockidx = offset / PAGE_QUEUES;` 效果的程式碼。 ```c= #include <stdint.h> #define ffs32(x) ((__builtin_ffs(x)) - 1) size_t blockidx; size_t divident = PAGE_QUEUES; blockidx = offset >> ffs32(divident); divident >>= ffs32(divident); switch (divident) { CASES } ``` 其中 CASES 可能形如: ```c case 2: blockidx /= 2; break; ``` CASES:`3`、`5`、`7` ### 理解 ```c=2 #define ffs32(x) ((__builtin_ffs(x)) - 1) ``` `__builtin_ffs(x)`:Returns one plus the index of the ==least significant 1-bit== of x, or if x is zero, returns zero. 這邊再做一個 `-1`的動作,就可以得出最右的 bit 1 右邊又有幾個 bit 0。 ``` 12 = (1100) __builtin_ffs(12) = 3 __builtin_ffs(12) - 1 = 2 故從右邊數會先經過 2 個 bit 0,來到第三個 bit 才會為 bit 1。 ``` ```c=3 blockidx = offset >> ffs32(divident); divident >>= ffs32(divident); ``` 原本 `blockidx` 的計算為 $\dfrac{offset}{PAGE-QUEUES}$, 這裡每次都是處理$2^n$,也就是 $\dfrac{\dfrac{offset}{2^n}}{\dfrac{PAGE-QUEUES}{2^n}}$ 處理。 程式中的 line 2 就是再找 $2^n$,找到我們就直接右移處理,不必使用除法。 回頭看 `PAGE_QUEUES` 的可能範圍: - (1 or 2 or 3 or 4) - (5 or 6 or 7 or 8) × ($2^n$), n from 1 to 14 觀察 `ffs32(divident)`: | divident(PAGE_QUEUES) | 二進制 | ffs32(divident) | divident >>= ffs32(divident) | -------- | -------- | -------- |-------- | | 1 | (0001)~2~ | 0 | (0001)~2~ = 1 | | 2 | (0010)~2~ | 1 | (0001)~2~ = 1 | | 3 | (0011)~2~ | 0 | (0011)~2~ = 3 | | 4 | (0100)~2~ | 2 | (0001)~2~ = 1 | | 5 x $2^n$ | (01010...0)~2~| n | (0101)~2~ = 5 | | 6 x $2^n$ | (01100...0)~2~| n+1 | (0011)~2~ = 3 | | 7 x $2^n$ | (01110...0)~2~| n | (0111)~2~ = 7 | | 8 x $2^n$ | (10000...0)~2~| n+3 | (0001)~2~ = 1 | 所以`divident >>= ffs32(divident)` 只會有`1`、`3`、`5`、`7`這三種可能。 由於已經沒辦法使用右移來代替除法運算,所以依照不同可能進行除法運算。 這裡的 `swithch` 應為: ```c switch (divident) { case 1: blockidx /= 1; break; case 3: blockidx /= 3; break; case 5: blockidx /= 5; break; case 7: blockidx /= 7; break; } ``` 而又因為 `/1` 其實對數字本身沒有作用,所以只需處理`3`、`5`、`7`的部分即可。 -------------------------------------------- ###### tags: `sysprog2020`