# 2020q3 Homework4 (quiz4) ###### Contributed by < `haung-me` > ###### tags: `embedded_master` # 測驗1 Hamming Distance ```c int hammingDistance(int x, int y) { return __builtin_popcount(x ^ y); } ``` 因為 hamming distance 就是要找出兩個數值在2進位時數值不一樣的 bit 個數,將輸入的兩個數做 XOR 後,就只有兩個數值不同的 bit 才會被 set 為1,此時再利用 __builtin_popcount 就可以計算出正確的 hamming distance。 ## 不使用 gcc extension 的 C99 實作程式碼 ```c int my_hamming(int x, int y) { x ^= y; y = 0; while (x) { x &= (x - 1); y++; } return y; } ``` 利用傳入數字的空間先計算 x ^ y 並且存在 x 原本的位置,y 則設定為0,迴圈計算 x 中有多少 set bits 並將結果記錄於 y 中。 - Leetcode 結果 ![](https://i.imgur.com/Y2yH0uM.png) ## LeetCode 477. Total Hamming Distance ```c int totalHammingDistance(int* nums, int numsSize){ int total=0; for(int i=0;i<numsSize;i++){ for(int j=0;j<numsSize;j++) { if(i >= j) continue; total += __builtin_popcount(nums[i] ^ nums[j]); } } return total; } ``` - 一開始的想法就是利用兩個 for 迴圈找尋所有的 couple,並且利用 __builtin_popcount 計算兩個數字的 Hamming Distance 再相加。 - 不過經過嘗試之後發現這樣子兩個 for 迴圈的做法執行時間太高,因此在 leetcode 會遭遇 Time Limit Exceeded ```c int totalHammingDistance(int* nums, int numsSize){ int setbits[32]; memset(setbits, 0, sizeof(int) * 32); int total = 0; for(int i=0;i<numsSize;i++){ while(nums[i]){ setbits[__builtin_ctz(nums[i])]++; nums[i] &= (nums[i] - 1); } } for(int i=0;i<32;i++){ total += (setbits[i] * (numsSize - setbits[i])); } return total; } ``` ![](https://i.imgur.com/CW0cajd.png) - 之後不管怎麼思考,如果要使用找尋兩個計算 distance 的話,一定需要使用兩個 for 迴圈,因此改變想法為:先計算資料集中各個 bit 各有幾個資料是1,最後再將 setbit 數量乘以此 bit 為0的數量即為當前 bit 的 distance。 ## Hamming Distance/Weight in ECC > Hamming Distance: Number of diffierent bits between two vector. > Hamming weight: Number of set bits in the vector. - 兩個向量 a, b 的 Hamming Distance 等於兩個向量的 minimun Hamming Weight,因為 Hamming Distance 等於 a+b 的 Hamming Weight。 - 在 $3*7$ 的矩陣 H 中,minimun Hamming Distance 以及 minimun Hamming Weight 都在 null space 中。因此,如果我們想要找到 null space 的其中一個向量,就至少要有 minimun Hamming Distance 個 set bits。 ![](https://i.imgur.com/G7QFY8N.png) - 另一方面,如果我們希望找出 t 個錯誤,兩個向量間的距離必須要大於等於 2t + 1,因為如果距離小於 2t + 1 就有可能兩個向量有所重疊,如果錯誤在重疊區域內的話,無法辨別哪一個向量的內容才是正確的。 :::success 綜合以上兩點,我們可以得到以下結論: 將 Hamming Distance 代入 2t + 1 中,即可找到相對應的 t 值,同時也是可以找到的錯誤數量至多 t 個。 ::: ### BCH code ![](https://i.imgur.com/MYiBRZn.png) > [Hamming codes, h■w to ov■rco■e n■ise.](https://youtu.be/X8jsijhllIA) - 資料集中除了放資料,也加入一些判斷是否有資料傳輸錯誤的 bit,在上圖中 15 個 bit 中有 5 個 bit 就是用來判斷錯誤,而不同的 bit 判斷的範圍也不相同。 > 0: 判斷整個資料集中是否有兩個錯誤存在 > 1: 000==1== 判斷最後一個 bit 是否為 1 > 2: 00==1==0 判斷倒數第二個 bit 是否為 1 > 4: 0==1==00 判斷第二個 bit 是否為 1 > 8: ==1==000 判斷第一個 bit 是否為 1 - 利用以上 5 個不同的 bit 不僅可以確定有資料傳輸錯誤,經過交叉比對還可以確定錯誤的 bit 為哪一個。 ### Encoding `hammingCode` 一開始設定為 0b000x0xxx0xxxxxxx,x 取決於要傳輸資料的內容。 ```c hammingCode |= \ (__builtin_popcount(0x5555 & hammingCode) & 1) << 14; hammingCode |= \ (__builtin_popcount(0x3333 & hammingCode) & 1) << 13; hammingCode |= \ (__builtin_popcount(0x0f0f & hammingCode) & 1) << 12; hammingCode |= \ (__builtin_popcount(0x00ff & hammingCode) & 1) << 10; hammingCode |= \ (__builtin_popcount(0xefff & hammingCode) & 1) << 15; ``` 計算資料在各區塊中的 setbits 數量,並且使得每個區塊中的 setbits 都為偶數個。 ### Decoding ![](https://i.imgur.com/y0IpGsZ.png) > [Hamming codes, h■w to ov■rco■e n■ise.](https://youtu.be/X8jsijhllIA) - 將資料集中所有的 setbits 位置進行一次 XOR 就可以計算出有錯誤的 bit,重複執行此步驟直到沒有計算完的結果為 0 即代表資料正確。 ```c int tmp = hammingCode; int toggle = 0; while (tmp) { toggle ^= __builtin_ctz(tmp); tmp &= (tmp - 1); } if (toggle != 0) hammingCode ^= (1 << toggle); ``` ## Reed-solomon 我們要傳送的資料集為 $C = {c_1, c_2, ..., c_k}$,總共有 k 個數字,傳送時我們需要加上 m 個 parity bit 使得接收訊息端 decode 時可以找到錯誤並且修正。 ### Encoding 傳送資料前,我們要先把資料的數字集代入 $f(x) = \sum_{i=0}^{n-1}c_ix^i$,此時可以得到一個多項式 (polynomial)。 同時,再代入另一個函式 $g(x) = \prod_{i=0}^{n-k}(x - \alpha^j)$ > $\alpha$ is a [primitive root](https://brilliant.org/wiki/primitive-roots/), and $\prod$ means product of all elements in the set. 而 $S(x) = f(x)x^m - [(f(x)x^m) \mod g(x)]$,且可以整理為 $S(x) = \sum_{i=0}^{n-1}s_ix^i$ ==上述 $S(x)$ 的係數即為我們要傳送出去的資料。== ### Decoding 假設接受端接收到的資料為 $r(x) = \sum_{i=0}^{n-1}r_ix^i = \sum_{i=0}^{n-1}(S(x^i) + e(x^i))$ ==$e(x)$ means the error while transmitting== 如果將任意一個 primitive root 代入 x 中,$S(x)$ 為 0,因此 $r(\alpha) = \sum_{i=0}^{n-1}(0 + e(\alpha^i)) = \sum_{i=0}^{n-1}e(\alpha^i)$ 因此可以把結果組合為以下矩陣以計算並修復為正確的值 \begin{equation} % matrix alpha \begin{bmatrix} \alpha^{0\times0} & \alpha^{1\times0} & ... & \alpha^{(n-1)\times0} \\ \alpha^{0\times1} & \alpha^{1\times1} & ... & \alpha^{(n-1)\times1} \\ \vdots & \vdots & \ddots & \vdots \\ \alpha^{0\times(n-1)} & \alpha^{1\times(n-1)} & ... & \alpha^{(n-1)\times(n-1)} \\ \end{bmatrix} % matrix error \begin{bmatrix} e_0 \\ e_1 \\ \vdots \\ e_{n-1}\\ \end{bmatrix}= % matrix s \begin{bmatrix} S_0 \\ S_1 \\ \vdots \\ S_{n-1} \\ \end{bmatrix} \end{equation} - 在加入 m 個 parity bit 時,最多可以找出 $\lfloor \frac{m}{2} \rfloor$ 個錯誤的 bit 並且修正為正確的資料,或者找出 m 個錯誤不過不知道位置。 # 測驗2 Tree Ancestor ```c typedef struct { int **parent; // Array to store the parents of the node int max_level, n; // Total depth of the Tree, and number of nodes } TreeAncestor; ``` - Define the structure of TreeAncestor ```c 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; obj->n = n; return obj; } ``` 1. 先建立一個 n $\times$ max_level 的 2D array 並且初始化為 -1,之後用來存取各個 node 的 parent。 2. 把輸入的 parents array 直接先放進去 2D array 的第一個 element。 3. 每往後一個 column 代表上一層的 parent,因此只有前一個 column 不為 -1 才有可能有更上一層的 column,找尋上一層的 column 要從當前位置的 parent 往前找一個, 因此 `obj->parent[i][j] = obj->parent[obj->parent[i][j - 1]][j - 1]` ```c 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; } ``` - 如果 `k & (1 << i) == 1` 代表可以先找尋第 i 個 parent 是哪一個 node,之後再從第 i 個 parent 往上找。 ## treeAncestorCreate 記憶體浪費測試與修正 ### Memory leak 拿老師的 code 直接 compile 會有 struct TreeAncestor 沒有定義 obj->n 的 error,一開始想說可能是老師輸入錯誤,因此直接把 obj->n 更改為 obj->max_level,在做記憶體測試的時候發現有 memory leak。 ``` $ valgrind --leak-check=full ./ancestor ==4290== Memcheck, a memory error detector ==4290== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==4290== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==4290== Command: ./ancestor ==4290== ==4290== ==4290== HEAP SUMMARY: ==4290== in use at exit: 64 bytes in 4 blocks ==4290== total heap usage: 9 allocs, 5 frees, 184 bytes allocated ==4290== ==4290== 64 bytes in 4 blocks are definitely lost in loss record 1 of 1 ==4290== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==4290== by 0x10920C: treeAncestorCreate (ancestor.c:16) ==4290== by 0x10950E: main (ancestor.c:59) ==4290== ==4290== LEAK SUMMARY: ==4290== definitely lost: 64 bytes in 4 blocks ==4290== indirectly lost: 0 bytes in 0 blocks ==4290== possibly lost: 0 bytes in 0 blocks ==4290== still reachable: 0 bytes in 0 blocks ==4290== suppressed: 0 bytes in 0 blocks ==4290== ==4290== For lists of detected and suppressed errors, rerun with: -s ==4290== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) ``` 因為宣告了 n 個 array 存取每個 node 的部分 ancestor,不過如果只 free max_level 個就必定有部分記憶體沒有被歸還,因此修改 struct TreeAncestor 增加一個 int 存取整個 tree 的 node 數量。 ``` $ valgrind --leak-check=full ./ancestor ==17441== Memcheck, a memory error detector ==17441== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==17441== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==17441== Command: ./ancestor ==17441== ==17441== ==17441== HEAP SUMMARY: ==17441== in use at exit: 0 bytes in 0 blocks ==17441== total heap usage: 9 allocs, 9 frees, 184 bytes allocated ==17441== ==17441== All heap blocks were freed -- no leaks are possible ==17441== ==17441== For lists of detected and suppressed errors, rerun with: -s ==17441== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) ``` 修改完之後就沒有 memory leak 了。 ### Memory Waste #### Attempt 1 其實每個 node 只要記憶一個 parent 並且找尋的時候重複執行 k 次就可以找到 Kth ancestor。 ```c 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 < parentSize; i++) obj->parent[i] = parent[i]; obj->max_level = max_level - 1; obj->n = n; return obj; } int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k) { int max_level = obj->max_level; while (k) { node = obj->parent[node]; k -= 1; if (node == -1 && k > 0) return -1; } return node; } void treeAncestorFree(TreeAncestor *obj) { free(obj->parent); free(obj); return; } ``` 不過毫不意外的,執行時間已經超過 leetcode 的限制,畢竟在 testcase 9 就已經有 50000 筆資料了。 ![](https://i.imgur.com/dUk8I8D.png) #### Attempt 2 嘗試直接把所有的 parent 都放在一個 array 裡面,希望能夠提升找尋 ancestor 的速度 ```c typedef struct { int **parent; int *num_parents; } TreeAncestor; TreeAncestor *treeAncestorCreate(int n, int *parent, int parentSize) { TreeAncestor *obj = malloc(sizeof(TreeAncestor)); obj->parent = malloc(n * sizeof(int *)); obj->num_parents = malloc(n * sizeof(int)); for (int i = 0; i < n; i++) { if (i == 0) obj->parent[i] = malloc(sizeof(int)); else obj->parent[i] = malloc((obj->num_parents[parent[i]] + 1) * sizeof(int)); obj->parent[i][0] = parent[i]; if (i != 0) { int j = 1; for (j = 1; j < obj->num_parents[parent[i]]; j++) { obj->parent[i][j] = obj->parent[parent[i]][j - 1]; } obj->parent[i][j] = -1; obj->num_parents[i] = obj->num_parents[parent[i]] + 1; } else obj->num_parents[i] = 1; } return obj; } int treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k) { if (k >= obj->num_parents[node]) return -1; return obj->parent[node][k - 1]; } void treeAncestorFree(TreeAncestor *obj) { for (int i = 0; i < obj->num_parents[i]; i++) free(obj->parent[i]); free(obj->parent); free(obj->num_parents); free(obj); } ``` 不過卻造成建立 tree 所需要的時間過長,甚至執行的時間比一次往上走一層更慢。 ![](https://i.imgur.com/V9cFweM.png) - 從 callgrind 可以看出 create 占了 99.99% 的 instructions #### Attempt 3 此時,再嘗試回到老師使用的 Binary lifting 的方式,把多餘的最後一個 -1 刪除,因為每個 node 最後都記憶 -1 單純為了確認停止點。 ```c int max_level = 32 - __builtin_clz(n); ... obj->max_level = max_level; ``` 提交到 leetcode 後,記憶體用量確實減少了,不過執行時間也進步了一些些。 ![](https://i.imgur.com/HmbFniU.png) - 在思考如何提升執行時間時,發現`YLowy`同學刪除多餘的 -1 之後竟然執行時間就進步到 81%,不過我的卻只有 58.56%,於是我開始找尋我們之間的差別,發現他在最後 free 的時候並沒有將所有的 `obj->parent[i]` 記憶體歸還,我在前面提到過這個問題。既然 leetcode 不管有沒有 free 記憶體那時不是直接不歸還記憶體就好 :rolling_on_the_floor_laughing: ```c void treeAncestorFree(TreeAncestor *obj) { } ``` - 把 free 內的動作全部刪除,變成上面這樣還真的進步到 90% :face_palm: ![](https://i.imgur.com/qJ6g94D.png) - 初始化每個 node 的第一個 parent 就要自己一個 for 迴圈實在是有點浪費,因此我選擇直接把初始化的動作放到前面 malloc 記憶體的 for 迴圈裡面。 ```c 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; obj->parent[i][0] = parent[i]; } ``` ![](https://i.imgur.com/O66P3Sh.png) 原來光是減少一個 for 迴圈竟然可以縮減這麼多的執行時間 # 測驗3 FizzBuzz - 此題運用到之前的除法計算加速方法,只要先將 `0xFFFFFFFF / n + 1` 計算完成並且儲存好就不需要一直進行除法的運算,畢竟除法需要更多的 instruction。把前面計算好的 mask 乘上要判斷的數值後,如果此數不是 n 的倍數的話就會得到一個很大的值(正或負),反之則會得到比較小的值(小於 mask 本身)。 - `length = (2 << div3) << div5` 可以利用 shift 的方式就計算 output 的長度,如果都不是的話只需要 2 bit,是 3 或 5 其中一個的倍數的話則需要 `2 << 1` 個 bit,同時是 3 和 5 的倍數則需要 `2 << 2` 個 bit。 - `(MSG_LEN >> div5) >> (div3 << 2)` 可以控制字串的開始位置。如果有 3 這個因數就應該要右移 4 次,使得起始點為0,而如果因數有 5,則需要右移一次,兩個都是就需要 4 次(不過 5 次也不會影響)。 ![](https://i.imgur.com/kZ7Lj5u.png) 利用 callgrind 可以看出 naive 以及 bitwise 兩個方法的 Ir per call 幾乎差了一倍。 # 測驗4 Page_queues 因為在進行 `divident >>= ffs32(divident)` 時,我們已經把所有的 2 的倍數都消除了,且 page_queue 最大就是 $8\times2^{n}$,因此後面 `switch (divident)` 就只需要判斷 3, 5, 7 即可。 ## Leetcode 51. N-Queens