# [2020q1](http://wiki.csie.ncku.edu.tw/linux/schedule) 第 3 週測驗題 ###### tags: `linux2020` :::info 目的: 檢驗學員對 **[linked list 和非連續記憶體操作](https://hackmd.io/@sysprog/c-linked-list)**, **[bitwise 操作](https://hackmd.io/@sysprog/c-bitwise)**, **[指標操作](https://hackmd.io/@sysprog/c-pointer)** 的認知 ::: ==[作答表單](https://forms.gle/puNw7kvhDwtzhwcV9)== --- ### 測驗 `1` XOR 運算特性: * $A \oplus A = 0$ * $A \oplus 0 = A$ * $A \oplus 1 = \neg A$ * $(A \oplus B) \oplus B = A$ 以下程式碼是個 [XOR linked list](https://en.wikipedia.org/wiki/XOR_linked_list) 的實作,在這樣的資料結構可表示為: ``` 1 2 3 4 data HAT CAT EAT BAT link 2 2 6 3 ``` 要往下一格移動時,我們需要前一格在哪和這一格的位置。例如我們現在在 `HAT` (1), 已知前一格是 (0),那麼下一格就是 `link(1) XOR 0` = `2 XOR 0` = 2,也就是 `CAT`。繼續,現在位於 `CAT` (2),前一格在 (1),於是下一格就是 `link(2) XOR 1` = `2 XOR 1` = 3,亦即 `EAT`,依此類推。只要當找出來的下一格是 (0) 就結束。 ```cpp #include <stdint.h> typedef struct __list list; struct __list { int data; struct __list *addr; }; #define XOR(a, b) ((list *) ((uintptr_t) (a) ^ (uintptr_t) (b))) void insert_node(list **l, int d) { list *tmp = malloc(sizeof(list)); tmp->data = d; if (!(*l)) { tmp->addr = NULL; } else { (*l)->addr = XOR(tmp, (*l)->addr); tmp->addr = *l; } *l = tmp; } void delete_list(list *l) { while (l) { list *next = l->addr; if (next) next->addr = XOR(next->addr, l); free(l); l = next; } } ``` 在 `__list` 結構體內雖僅有一個 `struct __list *addr` 指標型態的物件,但卻可實作雙向的 linked list,就是憑藉著上述的實作技巧。[XOR linked list](https://en.wikipedia.org/wiki/XOR_linked_list) 的優勢很顯著,對空間的佔用更為精簡: ![](https://i.imgur.com/hy1uu2r.png) 隨著資料處理量的增長,效益更明顯: ![](https://i.imgur.com/ZvpHmSk.png) 與尋常作法相比,佔用的儲存空間比值會趨近 $67\%$。 接著我們嘗試撰寫針對 [XOR linked list](https://en.wikipedia.org/wiki/XOR_linked_list) 的排序程式,採用遞增順序: ```cpp list *sort(list *start) { if (!start || !start->addr) return start; list *left = start, *right = start->addr; left->addr = NULL; right->addr = XOR(right->addr, left); left = sort(left); right = sort(right); for (list *merge = NULL; left || right;) { if (!right || (left && left->data < right->data)) { list *next = left->addr; if (next) next->addr = XOR(left, next->addr); if (!merge) { start = merge = left; merge->addr = NULL; } else { merge->addr = LL1; left->addr = LL2; merge = left; } left = next; } else { list *next = right->addr; if (next) next->addr = XOR(right, next->addr); if (!merge) { start = merge = right; merge->addr = NULL; } else { merge->addr = RR1; right->addr = RR2; merge = right; } right = next; } } return start; } ``` 請補完上述排序程式碼。 ==作答區== LL1 = ? * `(a)` `right` * `(b)` `left` * `(c)` `merge` * `(d)` `XOR(merge, left)` * `(e)` `XOR(merge, right)` * `(f)` `XOR(merge, left->addr)` * `(g)` `XOR(merge, right->addr)` * `(h)` `XOR(merge->addr, left)` * `(i)` `XOR(merge->addr, right)` LL2 = ? * `(a)` `right` * `(b)` `left` * `(c)` `merge` * `(d)` `XOR(merge, left)` * `(e)` `XOR(merge, right)` * `(f)` `XOR(merge, left->addr)` * `(g)` `XOR(merge, right->addr)` * `(h)` `XOR(merge->addr, left)` * `(i)` `XOR(merge->addr, right)` RR1 = ? * `(a)` `right` * `(b)` `left` * `(c)` `merge` * `(d)` `XOR(merge, left)` * `(e)` `XOR(merge, right)` * `(f)` `XOR(merge, left->addr)` * `(g)` `XOR(merge, right->addr)` * `(h)` `XOR(merge->addr, left)` * `(i)` `XOR(merge->addr, right)` RR2 = ? * `(a)` `right` * `(b)` `left` * `(c)` `merge` * `(d)` `XOR(merge, left)` * `(e)` `XOR(merge, right)` * `(f)` `XOR(merge, left->addr)` * `(g)` `XOR(merge, right->addr)` * `(h)` `XOR(merge->addr, left)` * `(i)` `XOR(merge->addr, right)` :::success 延伸問題: 1. 解釋上述 [XOR linked list](https://en.wikipedia.org/wiki/XOR_linked_list) 排序實作的原理,並指出其中可改進之處; 2. 在 Wikipedia 關於合併排序的詞目中,談及 [Optimizing merge sort](https://en.wikipedia.org/wiki/Merge_sort#Optimizing_merge_sort),我們可知道一種最佳化策略是:在子序列大小達到 $S$ 時就不再繼續分割,此時這個 $S$ 為可完整放進處理器 cache 中的元素數量。每個大小為 $S$ 的子序列,都透過適用於小規模 in-place (即不需要額外配置記憶體) 排序演算法(如插入排序或泡沫排序)進行排序,以減少在記憶體內交換特定區域的次數。在小型子序列排序完畢後,再以典型的遞迴型合併排序完剩下的部份。 若可找到一個恰當的 $S$, 便能有效的同時利用到插入排序和合併排序的優點,並減少原始的合併排序當中 locality 不好的影響。請觀察你的硬體組態 (如 L1 data cache 的容量和行為),以上述策略實作效率更好的 [XOR linked list](https://en.wikipedia.org/wiki/XOR_linked_list) 排序程式。過程中應該充分量化數據並進行視覺化處理。 3. 修改 [lab0-c](https://github.com/sysprog21/lab0-c) 裡頭的 [harness.c](https://github.com/sysprog21/lab0-c/blob/master/harness.c),將原本內部使用的 doubly-linked list 換為 [XOR linked list](https://en.wikipedia.org/wiki/XOR_linked_list),觀察記憶體佔用率的變化,可設計頻繁的 `test_malloc` / `test_free` 呼叫交錯情境。 ::: --- ### 測驗 `2` 考慮以下 singly-linked list 程式: ```cpp #include <stddef.h> struct node { int data; struct node *next; } *head; void insert(struct node *newt) { struct node *node = head, *prev = NULL; while (node != NULL && node->data < newt->data) { prev = node; node = node->next; } newt->next = node; if (prev == NULL) head = newt; else prev->next = newt; } ``` 可透過以下 pointer to pointer 改寫 `insert` 函式,功能等價但更簡潔,如下: ```cpp void insert(struct node *newt) { struct node **link = &head; while (*link && AA) BB; newt->next = *link; *link = newt; } ``` 請補完程式碼。 ==作答區== AA = ? * `(a)` `(link)->data < newt->data` * `(b)` `*link->data < newt->data` * `(c)` `(*link)->data < newt->data` * `(d)` `**link->data < newt->data` BB = ? * `(a)` `link = &(*link)->next` * `(b)` `*link = *link->next` * `(c)` `(*link) = (*link)->next` * `(d)` `link = (*link)->next` :::success 延伸問題: 1. 解釋程式運作原理、對執行時間的影響 (考慮到 [branch predictor](https://en.wikipedia.org/wiki/Branch_predictor) 行為),和指出其實作限制; 2. 在 Linux 核心找出運用類似的手法的程式碼並解說 * 提示: 可參見 [bfq_insert](https://elixir.bootlin.com/linux/v4.15/source/block/bfq-wf2q.c#L373) 和 [rb_link_node](https://elixir.bootlin.com/linux/v4.15/source/include/linux/rbtree.h#L105) 操作 [red-black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) 的手法 ::: ---