# 2023q1 Homework3 (quiz3)
contributed by < `ItisCaleb` >
## 測驗 `1`
透過 ABI 的特性,我們可以將父節點的地址儲存在該節點的 `color` 欄位中,同時使用 `color` 欄位的 LSB 來表示該節點的顏色。
接著,我們可以使用位元運算對 `color` 進行操作,以得到父節點的地址或是節點的顏色。
```c
typedef struct __node {
uintptr_t color;
struct __node *left, *right;
...
} node_t __attribute__((aligned(sizeof(long))));
```
```c
#define rb_parent(r) ((node_t *) ((r)->color & ~1))
#define rb_color(r) ((color_t) (r)->color & 1)
#define rb_set_parent(r, p) \
do { \
(r)->color = rb_color(r) | (uintptr_t) (p); \
} while (0)
#define rb_set_red(r) \
do { \
(r)->color &= ~1; \
} while (0)
#define rb_set_black(r) \
do { \
(r)->color |= 1; \
} while (0)
```
在 `cmap_insert` 中,需要先從根節點開始走訪節點,直到找到樹的底端或是 NULL,才能進行插入操作。
在走訪的過程中,`res` 是比較新元素和當前節點的值的結果,以確定是要往左邊走還是右邊走。如果最終找到了 NULL ,那麼就可以把新元素插入到這個位置上。
```c
if (res < 0) {
if (!cur->left) {
cur->left = node;
rb_set_parent(node, cur);
cmap_fix_colors(obj, node);
break;
}
cur = cur->left;
} else {
if (!cur->right) {
cur->right = node;
rb_set_parent(node, cur);
cmap_fix_colors(obj, node);
break;
}
cur = cur->right;
}
```
在 `tree_sort` 中,我們把 `list` 中的節點一個一個放進 `map` 裡,然後再透過`cmap_next` 一個一個拿出來,這樣就會是排序好的狀態
最後我們使用把 `list` 的結尾變成 NULL 並且指向第一個節點
```c
void tree_sort(node_t **list)
{
node_t **record = list;
cmap_t map = cmap_new(sizeof(long), sizeof(NULL), cmap_cmp_int);
while (*list) {
cmap_insert(map, *list, NULL);
list = &(*list)->next;
}
node_t *node = cmap_first(map), *first = node;
for (; node; node = cmap_next(node)) {
*list = node;
list = &(*list)->next;
}
*list = NULL;
*record = first;
free(map);
}
```
而在觀察程式碼的時候,也看到了一些可以改進的地方
`cmap_rotate_left/right` 的大部份程式碼幾乎一樣,所以把同樣的部份抽離出來到 `cmap_do_rotate`,原本的函式只留下調整要 rotate 的 node
```c
static node_t *cmap_rotate_left(cmap_t obj, node_t *node)
{
node_t *r = node->right, *rl = r->left;
/* Adjust */
r->left = node;
node->right = rl;
return do_cmap_rotate(obj,r,rl);
}
```
```c
static inline node_t *do_cmap_rotate(cmap_t obj, node_t *dir, node_t *ddir)
{
node_t *node = rb_parent(dir);
node_t *up = rb_parent(node);
rb_set_parent(dir, up);
rb_set_parent(node, dir);
if (ddir)
rb_set_parent(ddir, node);
if (up) {
if (up->right == node)
up->right = dir;
else
up->left = dir;
}
if (node == obj->head)
obj->head = dir;
return dir;
}
```
同樣注意到 `cmap_l_l` 跟 `cmap_r_r` 交換顏色的程式碼相同,於是也抽離出來到 `cmap_swap_color`
```c
static void cmap_r_r(cmap_t obj,
node_t *node UNUSED,
node_t *parent UNUSED,
node_t *grandparent,
node_t *uncle UNUSED)
{
/* Rotate to the left according to grandparent */
grandparent = cmap_rotate_left(obj, grandparent);
/* Swap grandparent and uncle's colors */
cmap_swap_color(grandparent,grandparent->left);
}
```
```c
static inline void cmap_swap_color(node_t *a, node_t *b){
color_t c1 = rb_color(a), c2 = rb_color(b);
if (c1 == CMAP_RED)
rb_set_red(b);
else
rb_set_black(b);
if (c2 == CMAP_RED)
rb_set_red(a);
else
rb_set_black(a);
};
```
## 測驗 `2`
priority queue 分別有 insert 跟 pop 的操作,並且各自有 balanced 跟 unbalanced 的版本
`avl_prio_queue_insert_unbalanced/balanced`
我們首先將 `cur_nodep` 指向 root
並且 `isminimal` 為確認要插入的 node 是否為最小的 flag
```c
static inline void avl_prio_queue_insert_unbalanced(
struct avl_prio_queue *queue,
struct avlitem *new_entry)
{
struct avl_node *parent = NULL;
struct avl_node **cur_nodep = &queue->root.node;
struct avlitem *cur_entry;
int isminimal = 1;
...
```
接著便用迭代的方式走訪樹,比現在的 node 小就往左走,否則就往右走
如果往右走就表示要插入的 node 不為最小的 node
```c
while (*cur_nodep) {
cur_entry = avl_entry(*cur_nodep, struct avlitem, avl);
parent = *cur_nodep;
if (cmpint(&new_entry->i, &cur_entry->i) <= 0) {
cur_nodep = &((*cur_nodep)->left);
} else {
cur_nodep = &((*cur_nodep)->right);
isminimal = 0;
}
}
```
如果是最小的 node,便更新 `queue->min_node`
```c
if (isminimal)
queue->min_node = &new_entry->avl;
```
最後我們便將要插入的 node 放到樹上
如果操作是 unbalanced,使用 `avl_link_node`,直接把 node 接上
```c
avl_link_node(&new_entry->avl, parent, cur_nodep);
```
而 balanced 則使用 `avl_insert`,在接上 node 之後,函式會去計算並旋轉樹來確保平衡
```c
avl_insert(&new_entry->avl, parent, cur_nodep, &queue->root);
```
`avl_prio_queue_pop_unbalanced/balanced`
pop 會回傳最小的 node
並且把 `queue->min_node` 更新成下一個 node
```c
static inline struct avlitem *avl_prio_queue_pop_balanced(
struct avl_prio_queue *queue)
{
struct avlitem *item;
if (!queue->min_node)
return NULL;
item = avl_entry(queue->min_node, struct avlitem, avl);
queue->min_node = avl_next(queue->min_node);
...
```
最後一樣
unbalanced 會使用 `avl_erase_node`
```c
avl_erase_node(&item->avl, &queue->root, &removed_right);
```
balanced 會使用 `avl_erase` 來確保樹是平衡的
```c
avl_erase(&item->avl, &queue->root);
```
## 測驗 `3`
LFSR利用一組 binary pattern 作為初始狀態,然後將這組 binary pattern 通過一系列的位元移位、XOR運算等操作,產生出一系列的新數列,這些新數列看起來就像是隨機產生的序列。而LFSR通過改變移位寄存器的位元個數、XOR運算的默認值可以控制生成的數列長度和隨機性質。
LFSR就是一種通過移位、XOR等操作,生成隨機序列的技術。
題目給的 LFSR 實作是一種 Fibonacci LFSR
在這個例子裡 bit 是從左數到右並且從 1 開始
我們把 LSB (64th bit) 及選定的幾個 bit 稱為 tap
總共有幾個步驟
- 給定一個 binary pattern
- 將 tap 照順序去 xor,在這例子中是 64、61、34、9 個
- 把這組 binary pattern 向右位移,並且將上一步輸出的 bit 放到最左邊
要注意的是選定的 tap 要符合兩項條件
- 數量為偶數
- 所有數字的最大公因數為 1 (整集互質 setwise coprime)
而要產生新的數組就是把輸出的 binary pattern 作為 LFSR 的輸入
```c
/* Implementation of LFSR (linear feedback shift register)
* on uint64_t using irreducible polynomial x^64 + x^61 + x^34 + x^9 + 1
* (On 32 bit we could use x^32 + x^22 + x^2 + x^1 + 1)
*/
static void lfsr(uint64_t *up)
{
uint64_t new_bit =
((*up) ^ ((*up) >> 3) ^ ((*up) >> 30) ^ ((*up) >> 55)) & 1u;
/* don't have to map '+1' in polynomial */
*up = ((*up) >> 1) | (new_bit << 63);
/* shift *up by 1 to RIGHT and insert new_bit at "empty" position */
}
```
在 Linux 的 `crypto/jitterentropy.c` 中的 `jent_lfsr_time` 可以看到 Fibonacci LFSR 的實作
```c
tmp = tmp >> (DATA_SIZE_BITS - 1);
/*
* Fibonacci LSFR with polynomial of
* x^64 + x^61 + x^56 + x^31 + x^28 + x^23 + 1 which is
* primitive according to
* http://poincare.matf.bg.ac.rs/~ezivkovm/publications/primpol1.pdf
* (the shift values are the polynomial values minus one
* due to counting bits from 0 to 63). As the current
* position is always the LSB, the polynomial only needs
* to shift data in from the left without wrap.
*/
tmp ^= ((new >> 63) & 1);
tmp ^= ((new >> 60) & 1);
tmp ^= ((new >> 55) & 1);
tmp ^= ((new >> 30) & 1);
tmp ^= ((new >> 27) & 1);
tmp ^= ((new >> 22) & 1);
new <<= 1;
new ^= tmp;
```
題目本身會分配數個 bucket ,並且透過 LSFR 來生成數組把這些 bucket 填滿
`N_BUCKETS` 是 bucket 的數
`N_BITS` 則是 `N_BUCKETS` 以 2 為底的 log
首先我們來看 `log2_64`
題目定義了一個 `log_table_256`,展開來就會是 0 ~ 255 以 2 為底的 log
```c
static const char log_table_256[256] = {
#define _(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
-1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3,
3, 3, 3, 3, 3, _(4), _(5), _(5), _(6), _(6), _(6),
_(6), _(7), _(7), _(7), _(7), _(7), _(7), _(7), _(7),
#undef _
};
```
而 `log2_64` 可以簡化成下列這樣
方法是先將整數向右位移 32 位,如果移動後的值不為零,就代表最高的 32 個 bit 中至少有一個 bit 是 1,此時可以將整數再向右位移 16 位,以此類推,每次將位移的距離除以 2。當位移的距離為 8 時,就可以找到最左邊有東西的 8 個 bit 及它們的位置。
接著,透過對照 `log_table_256`,就能找出這 8 個 bit 對應的 log 值
最後便將位置加上透過 `log_table_256` 找到的值
```c
uint64_t log2_64(uint64_t v){
int n = 32,r = 0;
for(;n>=8;n=n>>1){
if(v>>n){
v = v>>n;
r += n;
}
}
return r + log_table_256[v];
}
```
`bucket_number` 是用來把 LSFR 生成的數字限制在 `N_BUCKETS` 裡
`mask111` 是從 0~n 都為 1 的 bit pattern
`mask011` 則是從 0~n-1 都為 1 的 bit pattern
用 `mask111` 跟 `x` 去做 and 便可以得到`x` 0~n 的 bit pattern
如果這個數小於 `N_BUCKETS` 則回傳
而如果等於的話則將 `x` 向右位移 n+1 來獲取新的數組並且跟 `mask011` and 後回傳
```c
/* n == number of totally available buckets, so buckets = \{0, ...,, n-1\}
* ASSUME n < (1 << 32)
*/
unsigned int bucket_number(uint64_t x)
{
uint64_t mask111 = (1 << (N_BITS + 1)) - 1;
uint64_t mask011 = (1 << (N_BITS)) - 1; /* one 1 less */
unsigned char leq = ((x & mask111) < N_BUCKETS);
/* leq (less or equal) is 0 or 1. */
return (leq * (x & mask111)) +
((1 - leq) * ((x >> (N_BITS + 1)) & mask011));
/* 'x >> (N_BITS + 1)' : take different set of bits -> better uniformity.
* '... & mask011' guarantees that the result is less or equal N_BUCKETS.
*/
}
```
而最後的 `fill_buckets` 便是透過 LSFR 生成數組並且使用`bucket_number` 後將輸出放進 bucket裡面
## 測驗 `4`
這個函式的方法跟測驗 3 的很像,同樣是不斷去找最左邊有東西的 bits
不同的點在於 `r` 是用 bit operation 去得到 MSB 的位置
```c
int ceil_log2(uint32_t x)
{
uint32_t r, shift;
x--;
r = (x > 0xFFFF) << 4;
x >>= r;
shift = (x > 0xFF) << 3;
x >>= shift;
r |= shift;
shift = (x > 0xF) << 2;
x >>= shift;
r |= shift;
shift = (x > 0x3) << 1;
x >>= shift;
return (r | shift | x >> 1) + 1;
}
```
而當輸入為 0 的時候會輸出 32
要改進並且讓函式一樣是 branchless 可以套用上一題 `bucket_number` 最後一行使用的方法
```c
int ceil_log2(uint32_t x)
{
uint32_t r, shift, is_zero = !x;
x--;
r = (x > 0xFFFF) << 4;
x >>= r;
shift = (x > 0xFF) << 3;
x >>= shift;
r |= shift;
shift = (x > 0xF) << 2;
x >>= shift;
r |= shift;
shift = (x > 0x3) << 1;
x >>= shift;
return iszero * -1 + !is_zero * ((r | shift | x >> 1) + 1);
}
```