# 2023q1 Homework3 (quiz3) contributed by <`kart81604`> ## 測驗 `1` :::success AAAA = `r->color & ~7` BBBB = `r->color &= ~1` CCCC = `r->color |= 1` DDDD = `cur = cur->left` EEEE = `cur = cur->right` FFFF = `&(*list)->next` GGGG = `&(*list)->next` or `&node->next` 但後者答在老師的 google 表單不算正確 HHHH = `*list = NULL` ::: ### 研讀 [`tree_sort`](https://gist.github.com/jserv/920766d1fd893d0efd66ac7fdaf23401) ```c typedef struct __node { uintptr_t color; struct __node *left, *right; struct __node *next; long value; } node_t __attribute__((aligned(sizeof(long)))); ``` 樹中節點的型態上,要注意的是其中的 `color` ,如果只是代表顏色的話,大可只用 `bool` 來代表,為何還要用 `uintptr` 來儲存呢? 透過 include/linux/rbtree_types.h 中的 ```c struct rb_node { unsigned long __rb_parent_color; struct rb_node *rb_right; struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); ``` 原來這邊指的 `color` 並不是指該節點的顏色,而是它的 parent 的位子跟自身的顏色,一個值之所以可以同時給出兩個資料就是因為其中的 `__attribute__((aligned(sizeof(long))))` 64 位元的環境, `long` 的 size 為 8 bytes 代表每個節點的記憶體位址都會是 8 的倍數,用二位元方式儲存,該位址的後三個 bits 皆為 0 ,既然每個位址後面三個 bits 都為 0 ,那麼不用白不用,就順便拿來儲存顏色的資料,如果最右端的 bit 為 0 ,則代表該點為紅色,反之,則為黑色。 ```c #define rb_set_red(r) \ do { \ (color_t) (r)->color &= 0; \ } while (0) #define rb_set_black(r) \ do { \ (color_t) (r)->color |= 1; \ } while (0) ``` 上面兩個函數皆是對 `r` 的顏色作改變。 了解以上的結構及參數意義後,就可以知道如何取得該 node 的 parent 的記憶體位址,只要把該 node 的 `color` 後面三個 bits 改成 0 ,就可以了。 ```c #define rb_parent(r) ((node_t *) ((r)->color & ~7)) ``` 以下函式表示對紅黑樹插入新點 `node`。 `obj` 為紀錄整體紅黑樹的資訊,因插入新的點,所以 `obj->size` 要加 1 。 如果這紅黑樹為空,則新插入的點就為這紅黑樹的 root ,則該點改黑,再來對 `obj` 進行調整。 不為空就要找出新插入的點應該要在的位子,找到後插入進去,在對整體紅黑樹進行調整。 ```c static bool cmap_insert(cmap_t obj, node_t *node, void *value) { cmap_create_node(node); obj->size++; if (!obj->head) { /* Just insert the node in as the new head. */ obj->head = node; rb_set_black(obj->head); /* Calibrate the tree to properly assign pointers. */ cmap_calibrate(obj); return true; } /* Traverse the tree until we hit the end or find a side that is NULL */ for (node_t *cur = obj->head;;) { int res = obj->comparator(&node->value, &cur->value); if (!res) /* If the key matches something else, don't insert */ assert(0 && "not support repetitive value"); 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; } } cmap_calibrate(obj); return true; } ``` 以下為 `tree_sort` ,先把 `list` 中每個點插入到紅黑樹裡,在利用 `camp_first` 找出紅黑樹中最小的點,將該點記在 `first` 中,再利用 `camp_next(node)` 找出整個紅黑樹中,僅大於 node 的點,再放進 `first` 的下一個,重複此作法, `first` 就會是一個排序後的序列了。 ```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); } ``` ## 測驗 `2` :::success IIII = `node->parent_balance & ~7` JJJJ = `node->parent_balance & 3` KKKK = `node->parent_balance & 3` LLLL = `node->parent_balance & ~7` MMMM = `avl_rotate_right` NNNN = `avl_rotate_leftright` ::: 要取得該點的 parent ,方法與第一題的紅黑樹類似,該 node 因為有透過 `__attribute__((aligned(sizeof(unsigned long))))` 來對齊,因此後面三個 bits 都是 0 (這邊指的是 64 位元環境的情況下,如果是 32 位元,則是後面二個 bits 皆為 0 ,以下皆已 64 位元環境下去考慮),因此拿來儲存 AVL tree 平衡的資訊。 ```c static inline struct avl_node *avl_parent(struct avl_node *node) { return (struct avl_node *) (node->parent_balance & ~7); } ``` 要取得該點的 balance 資訊,要拿到該點的 `parent_balance` 的末 2 個 bits ,因此 ```c static inline enum avl_node_balance avl_balance(const struct avl_node *node) { return (enum avl_node_balance)(node->parent_balance & 3); } ``` 要改變 parent 只要保留原本的 balance 資訊,另外一邊,要改變 balance 資訊,要保留原本的 parent 資訊。 ```c static void avl_set_parent(struct avl_node *node, struct avl_node *parent) { node->parent_balance = (unsigned long) parent | (node->parent_balance & 3); } static void avl_set_balance(struct avl_node *node, enum avl_node_balance balance) { node->parent_balance = (node->parent_balance & ~7) | balance; } ``` 插入新點後,要調整樹來使得符合 AVL 的定義,從下例第 7 行開始,如果插入的點為 parent 的右子節點,要判斷 parent 的 balance 狀態,如果 parent 的 balance 為 `AVL_RIGHT` 則勢必要進行 `avl_rotate_left` 或是 `avl_rotate_rightleft` ,那要判斷要用哪個,就要看他右子節點的 balance ,如果為 `AVL_RIGHT` 或是 `AVL_NEUTRAL` 則進行前者,如果為 `AVL_LEFT` 則進行後者,做完之後就調整完畢。如果 parent 的 balance 狀態為 `NETURAL` ,則新加入右子節點會造成右偏重,則將 parent 的 balance 改成 `AVL_RIGHT`,然後進到下一個迴圈。如果 parent 為 `AVL_LEFT` 新加入點後會變成 `AVL_NEUTRAL` ,且整棵樹也調整完畢。 ```c= void avl_insert_balance(struct avl_node *node, struct avl_root *root) { struct avl_node *parent; /* go tree upwards and fix the nodes on the way */ while ((parent = avl_parent(node))) { if (avl_is_right_child(node)) { switch (avl_balance(parent)) { default: case AVL_RIGHT: /* compensate double right balance by rotation * and stop afterwards */ switch (avl_balance(node)) { default: case AVL_RIGHT: case AVL_NEUTRAL: avl_rotate_left(node, parent, root); break; case AVL_LEFT: avl_rotate_rightleft(node, parent, root); break; } parent = NULL; break; case AVL_NEUTRAL: /* mark balance as right and continue upwards */ avl_set_balance(parent, AVL_RIGHT); break; case AVL_LEFT: /* new right child + left leaning == balanced * nothing to propagate upwards after that */ avl_set_balance(parent, AVL_NEUTRAL); parent = NULL; break; } } else { switch (avl_balance(parent)) { default: case AVL_RIGHT: /* new left child + right leaning == balanced * nothing to propagate upwards after that */ avl_set_balance(parent, AVL_NEUTRAL); parent = NULL; break; case AVL_NEUTRAL: /* mark balance as left and continue upwards */ avl_set_balance(parent, AVL_LEFT); break; case AVL_LEFT: /* compensate double left balance by rotation * and stop afterwards */ switch (avl_balance(node)) { default: case AVL_LEFT: case AVL_NEUTRAL: avl_rotate_right(node, parent, root); break; case AVL_RIGHT: avl_rotate_leftright(node, parent, root); break; } parent = NULL; break; } } if (!parent) break; node = parent; } } ``` ## 測驗 `3` :::success AAAA = `56` BBBB = `48` CCCC = `40` DDDD = `32` EEEE = `24` FFFF = `16` GGGG = `8` HHHH = `0` IIII = `mask111` JJJJ = `mask011` ::: 以下的定義可以得知 `log_table_256[x]` 的值為 $\left \lfloor{log_2 x}\right \rfloor$ 。 ```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 _ }; ``` 利用上述的陣列來取得 64 位元的 input `v` ,來計算 $\left \lfloor{log_2 v}\right \rfloor$ ,因為 `log_table_256` 只能處理到 8 位元的 input ,因此要透過 bitwise 的操作,將數據縮減到 8 個,運作原理示範於程式碼下。 ```c uint64_t log2_64(uint64_t v) { unsigned r; uint64_t t, tt, ttt; ttt = v >> 32; if (ttt) { tt = ttt >> 16; if (tt) { t = tt >> 8; if (t) { r = 56 + log_table_256[t]; } else { r = 48 + log_table_256[tt]; } } else { t = ttt >> 8; if (t) { r = 40 + log_table_256[t]; } else { r = 32 + log_table_256[ttt]; } } } else { tt = v >> 16; if (tt) { t = tt >> 8; if (t) { r = 24 + log_table_256[t]; } else { r = 16 + log_table_256[tt]; } } else { t = v >> 8; if (t) { r = 8 + log_table_256[t]; } else { r = 0 + log_table_256[v]; } } } return r; } ``` ``` 1234 5678 9012 3456 ...... 1234 <-共 64 bits 表示 position 0110 0110 0011 0101 ...... 0101 <-input v //右移 32 bits 3456 7890 1234 5678 ...... 1234 <-共32 bits 從第33位元表示,因前32位元皆為0 0110 0110 0011 0101 ...... 0010 <-原本 v 的 32 bits 的 MSB //再右移 16 bits 9012 3456 7890 1234 ...... 1234 <-共16個bits 從第49位元表示,因前48位元皆為0 0110 0110 0011 0101 ...... 0001 <-原本 v 的 32 bits 的 MSB //再右移 8 bits 7890 1234 <-共8個 bits 從第57位元表示,因前56位元皆為0 0110 0110 <-原本 v 的 8 bits 的 MSB //將後面的 8 bits 值透過 log_table_256 去判斷此以二為底的log值再加上56 //得出 6+56 = 62 ``` `lsfr` 的運作為判斷 input `*up` ,該值的第 1 個、第 4 個、第 31 個、第 56 個 bit 作 exclusive or ,再將原本的 input 右移一位,再將 MSB 換成 `new_bit` 。 ```c 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 */ } ``` `bucket_number` 是用來計算當前 `x` 中 `N_BITS` bits LSB 是否小於 `N_BUCKET`,小於就回傳,大於的話就將 `x` 右移 `N_BITS + 1` 個 bits,再與 `mask011` 做 `&` ,來確保回傳的數字是小於 `N_BUCKETS` 。 透過這個函式,可以得到總是比 `N_BUCKETS` 的值。 ```c 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 & IIII)) + ((1 - leq) * ((x >> (N_BITS + 1)) & JJJJ)); /* 'x >> (N_BITS + 1)' : take different set of bits -> better uniformity. * '... & mask011' guarantees that the result is less or equal N_BUCKETS. */ } ``` `fill_bucket` 為將現有的 bucket 隨機填入數字,可以觀察到迴圈的次數為 `iterations` ,每一次都會挑一個 bucket 將它裡面的值增加一,在透過 `lfsr` 來更改 x 的數字,盡量讓下一輪挑到的 bucket 會趨近於亂數。 ```c void fill_buckets(unsigned int *buckets, unsigned int iterations) { uint64_t x = 0x98765421b16b00b5; unsigned char lfsr_iter = (N_BITS << 1); for (uint64_t i = 0; i < iterations; i++) { unsigned int tmp_bucket = bucket_number(x); *(buckets + tmp_bucket) = *(buckets + tmp_bucket) + 1; // 'turn handle' on LFSR lfsr_iteration-times! unsigned char ell = 0; while (ell < lfsr_iter) { lfsr(&x); ell++; } } } ``` 以下為 main function ,buckets 的個數有 20 個, iteraions 的次數為 $2^{20}$ 次, 9 - 15 行為配置記憶體,且也把這些 buckets 中的值歸零, `fill_buckets` 函式就是可以視為將 $2^{20}$ 分配給這 20 buckets 。 `evaluate_buckets` 為將每個 bucket 的值印出來,就不多作介紹。 ```c= int main() { int num_of_buckets = 120; /* an example of some non-power of 2 */ int num_of_iterations = (1 << 20); /* roughly 1 million */ set_N_BUCKETS(num_of_buckets); set_N_BITS(); unsigned int *buckets = malloc(N_BUCKETS * sizeof(unsigned int)); int i = 0; while (i < N_BUCKETS) { *(buckets + i) = 0; i++; } fill_buckets(buckets, num_of_iterations); evaluate_buckets(buckets); free(buckets); return 0; } ``` 以下為 ouput 。 ``` 0:9126 , 1:9309 , 2:9211 , 3:9250 , 4:9225 , 5:9183 , 6:9302 , 7:9228 , 8:9347 , 9:9298 , 10:9043 , 11:9237 , 12:9173 , 13:9373 , 14:9219 , 15:9136 , 16:9297 , 17:9155 , 18:9272 , 19:9354 , 20:9271 , 21:9091 , 22:9190 , 23:9265 , 24:9261 , 25:9110 , 26:9288 , 27:9151 , 28:9135 , 29:9337 , 30:8928 , 31:9077 , 32:9365 , 33:9249 , 34:9116 , 35:9220 , 36:9123 , 37:9400 , 38:9143 , 39:9241 , 40:9258 , 41:9233 , 42:9258 , 43:9236 , 44:9177 , 45:9181 , 46:9336 , 47:9370 , 48:9157 , 49:9207 , 50:9127 , 51:9142 , 52:9318 , 53:9200 , 54:9208 , 55:9237 , 56:9293 , 57:9234 , 58:9340 , 59:9250 , 60:9298 , 61:9246 , 62:9325 , 63:9188 , 64:8276 , 65:7839 , 66:8209 , 67:8089 , 68:8151 , 69:8152 , 70:8373 , 71:8140 , 72:8106 , 73:8165 , 74:8261 , 75:8121 , 76:8122 , 77:8192 , 78:8141 , 79:8314 , 80:8030 , 81:8161 , 82:8217 , 83:8326 , 84:8167 , 85:8202 , 86:8047 , 87:8064 , 88:8220 , 89:8301 , 90:8405 , 91:8140 , 92:8145 , 93:8215 , 94:8231 , 95:8267 , 96:8099 , 97:8160 , 98:8010 , 99:8113 , 100:8171 , 101:8298 , 102:8169 , 103:8236 , 104:8188 , 105:8162 , 106:8225 , 107:8257 , 108:8239 , 109:8248 , 110:8264 , 111:8148 , 112:8247 , 113:8062 , 114:8283 , 115:8170 , 116:8221 , 117:8060 , 118:8114 , 119:8125 , ``` ## 測驗 `4` :::success KKKK = `r | shift | x >> 1` ::: ```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; } ``` 從第 6 行開始看,如果 `x` 大於 `0xFFFF` ,則 `x` 16 bits 的 MSB 中必有 1 ,因此 $log_2 x$ 必定大於等於 16 ,那接下來要考慮的就是那 16 bits 的 MSB了,因此將 `x` 往右位移 16 個位元,反之,那如果是小於等於 `0xFFFF`,則 `x` 16 bits 的 MSB 全為 0,因此 $log_2 x$ 就必小於 16,那接下來要考慮的為 16 bits 的 LSB(`r` 此時為 0 ,因此不會位移)。`r` 為用來紀錄了共往右位移了多少位元。 在從第 8 行開始,目前要考慮的為 x 當前的 16 bits 的 LSB ,與 `0xFF` 進行比較,如果比較大,則進行位移,反之則不位移,再把位移值給加回 `r` ,重複此步驟與 `0xF` 、 `0x3` 、 `0x1` 比較。 最後就發現,為什麼沒有與 `0x1` 進行比較? 因為 `x >> 1` 這段就代表了與 `0x1` 作比較的行為。 而最後面 return 要加 1 是因為目標要求的是 $log_2 x$ 的上取整函數(ceil),加 1 這行為就是對 $log_2 x$ 進行無條件進位,但也會發現,如果 $log_2 x$ 原本就為整數,也會被加 1 ,導致輸出不符合預期的結果。因此第 5 行先將 x 減 1 ,來避免這樣的狀況。