---
# System prepended metadata

title: 2020q3 Homework4 (quiz4)
tags: [embedded_master]

---

# 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