# 2023q1 Homework1 (quiz1)
contributed by < [slipet](https://github.com/slipet) >
<details>
<summary><font color=darkred>開發環境</font></summary>
```
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 48 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 12s
On-line CPU(s) list: 0-11
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 5 5600X 6-Core Processor
CPU family: 25
Model: 33
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
Stepping: 0
Frequency boost: enabled
CPU max MHz: 4650.2920
CPU min MHz: 2200.0000
BogoMIPS: 7399.79
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr
sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl non
stop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 s
se4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy a
bm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_
nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ibpb stibp vmmcall
fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a rdseed adx smap clflushopt clwb sha
_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clz
ero irperf xsaveerptr rdpru wbnoinvd arat npt lbrv svm_lock nrip_save tsc_scale vmcb_cle
an flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ct
rl umip pku ospke vaes vpclmulqdq rdpid overflow_recov succor smca fsrm
Virtualization features:
Virtualization: AMD-V
Caches (sum of all):
L1d: 192 KiB (6 instances)
L1i: 192 KiB (6 instances)
L2: 3 MiB (6 instances)
L3: 32 MiB (1 instance)
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-11
```
</details>
## 測驗 1
<details>
<summary>details</summary>
```c
#include <stdint.h>
#include "list.h"
struct item {
uint16_t i;
struct list_head list;
};
static inline int cmpint(const void *p1, const void *p2)
{
const uint16_t *i1 = (const uint16_t *) p1;
const uint16_t *i2 = (const uint16_t *) p2;
return *i1 - *i2;
}
```
```diff
static void list_sort(struct list_head *head)
{
if (list_empty(head) || list_is_singular(head))
return;
struct list_head list_less, list_greater;
INIT_LIST_HEAD(&list_less);
INIT_LIST_HEAD(&list_greater);
- struct item *pivot = list_first_entry(head, AAA, BBB);
+ struct item *pivot = list_first_entry(head, item, list);
list_del(&pivot->list);
struct item *itm = NULL, *is = NULL;
- CCC (itm, is, head, list) {
+ list_for_each_entry_safe (itm, is, head, list) {
if (cmpint(&itm->i, &pivot->i) < 0)
- DDD (&itm->list, &list_less);
+ list_move (&itm->list, &list_less);
else
- EEE (&itm->list, &list_greater);
+ list_move (&itm->list, &list_greater);
}
list_sort(&list_less);
list_sort(&list_greater);
list_add(&pivot->list, head);
list_splice(&list_less, head);
- FFF(&list_greater, head);
+ list_splice_tail(&list_greater, head);
}
```
</details>
---
首先使用 `INIT_LIST_HEAD` 建立兩個空的鏈結串列 `list_less` 和 `list_greater` , 所有元素會跟分別跟 pivot 比較,較小的會放入 `list_less`,較大的則會放入 `list_greater`。
```graphviz
digraph G {
rankdir = LR
node[shape=record];
greater [label="greater", style=dashed, color=grey];
h [label="head", style=dashed, color=grey];
{
rank = same
less [label="less", style=dashed, color=grey];
}
subgraph cluster0 {
4->2->6->1->9;
}
h -> 4 [style=dashed, color=grey];
less -> greater [style=invis];
}
```
---
接著使用 `list_first_entry(head, type, member)` ,取出第一個元素作為 pivot , 我們可以從 `list_first_entry(head, type, member)` 的宣告發現,第二個參數為 data type,因此 AAA 為 struct item ,第三個參數為 member name, BBB 為 list 。
```graphviz
digraph G {
rankdir = LR
node[shape=record];
less [label="less", style=dashed, color=grey];
greater [label="greater", style=dashed, color=grey];
{
rank = same
h [label="head", style=dashed, color=grey];
pivot [label="pivot", style=dashed, color=grey];
}
subgraph cluster0 {
2->6->1->9;
}
h -> 2 [style=dashed, color=grey];
pivot -> 4;
less -> greater [style=invis];
}
```
---
走訪整個鏈結串列,每個元素都會跟 pivot 比較,分別取出並放入 `list_less` 或 `list_greater` 。我們會需要走訪整個串列並且取出元素,因此 CCC 會是 list_for_each_entry_safe , 從原本的串列取出並且放入其他串列使用 DDD = EEE = list_move , 由於整個排序需要是 stable sorting ,因此這邊不能使用 list_move_tail。
走訪鏈結串列後會如下所示:
```graphviz
digraph G {
rankdir = LR
node[shape=record];
less [label="less", style=dashed, color=grey];
greater [label="greater", style=dashed, color=grey];
{
rank = same
h [label="head", style=dashed, color=grey];
pivot [label="pivot", style=dashed, color=grey];
}
subgraph cluster0 {
less -> 2 -> 1
1 ->greater [style=invis];
greater -> 6 -> 9
}
pivot -> 4;
}
```
---
分別對 `list_less` 和 `list_greater` 做 `list_sort` ,以 top-down 的方式分解子問題直到鏈結為空或只剩一個 node 。 最後將排序好的 `list_less` 和 `list_greater` 和 `pivot` 接回 head 。
`list_sort(&list_less)` 和`list_sort(&list_greater)` 已經將 `list_less` 和 `list_greater` 排序完成並回傳:
```graphviz
digraph G {
rankdir = LR
node[shape=record];
less [label="less", style=dashed, color=grey];
greater [label="greater", style=dashed, color=grey];
{
rank = same
h [label="head", style=dashed, color=grey];
pivot [label="pivot", style=dashed, color=grey];
}
subgraph cluster0 {
less -> 1 -> 2
2 ->greater [style=invis];
greater -> 6 -> 9
}
pivot -> 4;
}
```
* `list_add(&pivot->list, head)` :
```graphviz
digraph G {
rankdir = LR
node[shape=record];
pivot [label="pivot", style=dashed, color=grey];
{
rank = same
h [label="head", style=dashed, color=grey];
}
subgraph cluster0 {
4
}
h -> 4 [style=dashed, color=grey];
pivot:s -> 4:n [style=dashed, color=grey];
}
```
* list_splice(&list_less, head):
```graphviz
digraph G {
rankdir = LR
node[shape=record];
less [label="less", style=dashed, color=grey];
pivot [label="pivot", style=dashed, color=grey];
{
rank = same
h [label="head", style=dashed, color=grey];
}
subgraph cluster0 {
1->2->4
}
h -> 1 [style=dashed, color=grey];
pivot:s -> 4:n [style=dashed, color=grey];
}
```
* 接著要把 `list_greater` 插入 pivot 後面,也就是插入 `head` 的尾端,因此這邊使用 `list_splice_tail` 將 `list_greater` 的 nodes 插入 head 的尾端。
```graphviz
digraph G {
rankdir = LR
node[shape=record];
less [label="less", style=dashed, color=grey];
greater [label="greater", style=dashed, color=grey];
pivot [label="pivot", style=dashed, color=grey];
{
rank = same
h [label="head", style=dashed, color=grey];
}
subgraph cluster0 {
1->2->4->6->9
}
h -> 1 [style=dashed, color=grey];
pivot:s -> 4:n [style=dashed, color=grey];
less -> greater [style=invis];
}
```
## 測驗 2
<details>
<summary>details</summary>
```c
#define MAX_DEPTH 512
static void list_sort_nr(struct list_head *head)
{
if (list_empty(head) || list_is_singular(head))
return;
struct list_head stack[MAX_DEPTH];
for (int i = 0; i < MAX_DEPTH; i++)
INIT_LIST_HEAD(&stack[i]);
int top = -1;
list_splice_init(head, &stack[++top]);
struct list_head partition;
INIT_LIST_HEAD(&partition);
while (top >= 0) {
INIT_LIST_HEAD(&partition);
list_splice_init(&stack[top--], &partition);
if (!list_empty(&partition) && !list_is_singular(&partition)) {
struct list_head list_less, list_greater;
INIT_LIST_HEAD(&list_less);
INIT_LIST_HEAD(&list_greater);
struct item *pivot =
list_first_entry(&partition, struct item, list);
list_del(&pivot->list);
INIT_LIST_HEAD(&pivot->list);
struct item *itm = NULL, *is = NULL;
GGGG (itm, is, &partition, list) {
list_del(&itm->list);
if (cmpint(&itm->i, &pivot->i) < 0)
list_move(&itm->list, &list_less);
else
list_move(&itm->list, &list_greater);
}
HHHH (&pivot->list, &list_less);
if (!list_empty(&list_greater))
list_splice_tail(&list_greater, IIII);
if (!list_empty(&list_less))
list_splice_tail(&list_less, JJJJ);
} else {
top++;
list_splice_tail(&partition, &stack[top]);
while (top >= 0 && list_is_singular(&stack[top])) {
struct item *tmp =
list_first_entry(&stack[top], struct item, list);
list_del(&tmp->list);
INIT_LIST_HEAD(KKKK);
list_add_tail(&tmp->list, head);
}
}
}
}
```
</details>
---
使用 `stack[MAX_DEPTH]` 模擬堆疊,一開始時將 `head` 放到 stack[0]:
```graphviz
digraph G {
rankdir = LR;
node [shape=record];
stack [label="<f5>stack[511]|<f4>... ...|<f3>stack[3]|<f2>stack[2]|<f1>stack[1]|<f0>stack[0]"];
subgraph cluster0 {
rank = same;
4->2->6->1->9;
}
stack:<f0> -> 4 [style=dashed, color=grey];
}
```
---
進入 `while-loop` 後先將第一個元素當作 pivot 取出,接著跟前面一樣跟 pivot 比大小將剩下的元素分別放入 `list_less` 或 `list_greater` ,因此 GGG = `list_for_each_entry_safe` 。
```graphviz
digraph G {
rankdir = LR
node[shape=record];
less [label="less", style=dashed, color=grey];
greater [label="greater", style=dashed, color=grey];
{
rank = same
pivot [label="pivot", style=dashed, color=grey];
}
subgraph cluster0 {
less -> 2 -> 1
1 ->greater [style=invis];
greater -> 6 -> 9
}
pivot -> 4;
}
```
---
接著我們會將 pivot 插入 less 的尾端,並且將 `list_less` 和 `list_greater` 分別放回堆疊。 HHHH = `list_move_tail` , IIII = &stack[++top] , JJJJ = &stack[++top]
```graphviz
digraph G {
rankdir = LR;
node [shape=record];
stack [label="<f5>stack[511]|<f4>... ...|<f3>stack[3]|<f2>stack[2]|<f1>stack[1]|<f0>stack[0]"];
6->9;
2->1->4;
stack:<f1> -> 2:w [style=dashed, color=grey];
stack:<f0> -> 6:w [style=dashed, color=grey];
}
```
---
我們不斷分解子問題直到 stack[top] 裡的 list 為空或是只有一個 node 後執行 `else` 的部份:
```graphviz
digraph G {
rankdir = LR;
node [shape=record];
stack [label="<f5>stack[511]|<f4>... ...|<f3>stack[3]|<f2>stack[2]|<f1>stack[1]|<f0>stack[0]"];
6->9;
stack:<f3> -> 1:w [style=dashed, color=grey];
stack:<f2> -> 2:w [style=dashed, color=grey];
stack:<f1> -> 4:w [style=dashed, color=grey];
stack:<f0> -> 6:w [style=dashed, color=grey];
}
```
在 `else` 裡的 `while-loop` 會不斷的將 stack pop 直到剩下未處理的子問題, pop 出來的 node 最後會插入 head 。 KKKK = &stack[top --] 。
```graphviz
digraph G {
rankdir = LR;
node [shape=record];
stack [label="<f5>stack[511]|<f4>... ...|<f3>stack[3]|<f2>stack[2]|<f1>stack[1]|<f0>stack[0]"];
h [label="head", style=dashed, color=grey];
6->9;
stack:<f0> -> 6:w [style=dashed, color=grey];
h->1->2->4;
}
```
---
我們走訪整個鏈結串列後,對於 pivot 來說,它已經是排序完成的 node , 因此再次加入 `list_less` 似乎不太合邏輯,會使得它在子問題中不斷被重複的比較。這邊改寫成 `list_splice_tail(&pivot->list, &stack[++top]);` 會比較恰當。
```diff
- list_move_tail (&pivot->list, &list_less);
if (!list_empty(&list_greater))
list_splice_tail(&list_greater, IIII);
+ list_move_tail (&pivot->list, &list_less);
```
```graphviz
digraph G {
rankdir = LR;
node [shape=record];
stack [label="<f5>stack[511]|<f4>... ...|<f3>stack[3]|<f2>stack[2]|<f1>stack[1]|<f0>stack[0]"];
6->9;
2->1;
stack:<f2> -> 2:w [style=dashed, color=grey];
stack:<f1> -> 4:w [style=dashed, color=grey];
stack:<f0> -> 6:w [style=dashed, color=grey];
}
```
## 測驗 3
從說明可以知道要獲得下一個 node 的位置,必須透過本身的位置跟前一個指標的位置做互斥或運算,我們可以從 `address_of` 可以看出來是用 `XOR_COMP` 運算後獲得位置,因為指標本身是不能做 +- 之外的運算的,我們必須要將指標轉型成 `uintptr_t` ,算完的結果再轉回 `xor_node_t *` 。LLL = (uintptr_t)(a) ^ (uintptr_t)(b)。
```c
#define XOR_COMP(a, b) ((xor_node_t *) ((uintptr_t)(a) ^ (uintptr_t)(b)))
static inline xor_node_t *address_of(xor_node_t *n1, xor_node_t *n2)
{
assert(n1 && n2);
return XOR_COMP(n1, n2);
}
```
---
從 `xor_node_t` 、 `xor_list_t` 、 `test_node` 的資料結構定義可以得知,在 `main` 中 list 和 test_node 經過初始化過後會如下所示:
```graphviz
digraph list_ele {
rankdir=LR;
node[shape=record];
subgraph cluster_0 {
label = "test_node"
test_node [label="value|{<addr>NULL}", style=bold];
}
subgraph cluster_1 {
rank = same
label = "list"
count [label="count=0", style=bold];
head [label="<head>head |<cmp> cmp", style=bold];
tail [label="<tail>tail |<cmp> cmp", style=bold];
head:cmp:e -> tail:tail:w[arrowhead=normal, arrowtail=normal];
tail:cmp:w -> head:head:e[arrowhead=normal, arrowtail=normal];
count -> head [style = invis]
}
test_node ->count [style = invis]
}
```
---
在 `for-loop` 中會不斷的新增 `test_node` 並且使用 `xorlist_add` 往 `list` 加入新的節點。
當 list 為空時,當我們呼叫 `xorlist_add` 加入節點, `xorlist_add` 中前半段程式碼我們可以如圖所示:
<details>
<summary>details</summary>
```c
int xorlist_add(xor_list_t *list, xor_node_t *n)
{
xor_node_t *real_next;
if (!n)
return ENOMEM;
xor_node_t *real_prev = &list->head;
xor_node_t *node = real_prev->cmp;
if (node == &list->tail)
real_next = MMMM;
else
real_next = node;
... ...
}
```
</details>
```graphviz
digraph list_ele {
rankdir=LR;
node[shape=record];
subgraph cluster_0 {
label = "xor_node_t"
new [label="{<addr>n}|{<cmp>cmp}", style=bold];
}
subgraph cluster_1 {
rank = same
label = "list"
head [label="<head>head |<cmp> cmp", style=bold];
tail [label="<tail>tail |<cmp> cmp", style=bold];
head:cmp:e -> tail:tail:w[arrowhead=normal, arrowtail=normal];
tail:cmp:w -> head:head:e[arrowhead=normal, arrowtail=normal];
count [label="count=0", style=bold];
tail -> count [style = invis]
}
n [label="node", style=dashed, color=grey];
n:e -> tail:tail:n [style=dashed, color=grey];
real_next [label="real_next", style=dashed, color=grey];
real_next:n->n:s[style = invis]
real_prev [label="real_prev", style=dashed, color=grey];
real_prev:e -> head:head:w [style=dashed, color=grey];
real_next:e -> tail:tail:n [style=dashed, color=grey];
new ->real_next [style = invis]
}
```
這裡我們可以發現, node 這個變數其實是多餘的, node 在這個函式只有只有用在 `if-else` 而且會等價於 `real_prev->cmp` 。
當我們要往 head 新增一個節點 n 時, `real_prev->cmp = n` 這行會將 `real_prev` 的 cmp 指到 n 也就是 head->cmp 。
```graphviz
digraph list_ele {
rankdir=LR;
node[shape=record];
real_next [label="real_next", style=dashed, color=grey];
n [label="node", style=dashed, color=grey];
subgraph cluster_1 {
rank = same
label = "list"
head [label="<head>head |<cmp> cmp", style=bold];
tail [label="<tail>tail |<cmp> cmp", style=bold];
new [label="<addr>n |<cmp> cmp", style=bold];
head:cmp:e -> new:addr:w[arrowhead=normal, arrowtail=normal];
tail:cmp:w -> head:head:e[arrowhead=normal, arrowtail=normal];
count [label="count=0", style=bold];
new -> tail [style = invis]
tail -> count [style = invis]
}
real_next:s -> tail:tail:n [style=dashed, color=grey];
n:e -> tail:tail:w [style=dashed, color=grey];
real_prev [label="real_prev", style=dashed, color=grey];
real_prev:e -> head:head:w [style=dashed, color=grey];
}
```
`n->cmp = XOR_COMP(real_prev, real_next)`: 利用 XOR_COMP 計算 n ^ head 得到下一個位置,用 `n->cmp` 只向下一個位置。
```graphviz
digraph list_ele {
rankdir=LR;
node[shape=record];
real_next [label="real_next", style=dashed, color=grey];
n [label="node", style=dashed, color=grey];
subgraph cluster_1 {
rank = same
label = "list"
head [label="<head>head |<cmp> cmp", style=bold];
tail [label="<tail>tail |<cmp> cmp", style=bold];
new [label="<addr>n |<cmp> cmp", style=bold];
head:cmp:e -> new:addr:w[arrowhead=normal, arrowtail=normal];
tail:cmp:w -> head:head:e[arrowhead=normal, arrowtail=normal];
new:cmp:e -> tail:tail:w[arrowhead=normal, arrowtail=normal]
count [label="count=0", style=bold];
new -> tail [style = invis]
tail -> count [style = invis]
}
real_next:s -> tail:tail:n [style=dashed, color=grey];
n:e -> tail:tail:w [style=dashed, color=grey];
real_prev [label="real_prev", style=dashed, color=grey];
real_prev:e -> head:head:w [style=dashed, color=grey];
}
```
`real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, PPPP))`: 這行是要將 tail 的 cmp 更新成插入的 n ,因此 PPPP = real_next->cmp。
```graphviz
digraph list_ele {
rankdir=LR;
node[shape=record];
real_next [label="real_next", style=dashed, color=grey];
n [label="node", style=dashed, color=grey];
subgraph cluster_1 {
rank = same
label = "list"
head [label="<head>head |<cmp> cmp", style=bold];
tail [label="<tail>tail |<cmp> cmp", style=bold];
new [label="<addr>n |<cmp> cmp", style=bold];
head:cmp:e -> new:addr:w[arrowhead=normal, arrowtail=normal];
new:cmp:e -> tail:tail:w[arrowhead=normal, arrowtail=normal]
count [label="count=0", style=bold];
new -> tail [style = invis]
tail -> count [style = invis]
}
real_next:s -> tail:tail:n [style=dashed, color=grey];
n:e -> tail:tail:w [style=dashed, color=grey];
real_prev [label="real_prev", style=dashed, color=grey];
real_prev:e -> head:head:w [style=dashed, color=grey];
tail:cmp:w -> new:addr:e[arrowhead=normal, arrowtail=normal];
}
```
最後更新 count = 1。
我們可以發現透過跟 prev 或 next 跟一個 cmp 指標做 XOR 運算就可以達到雙向的走訪鏈結串列,大大的減少記憶體的使用,但是缺點可能是程式碼的邏輯可能會變得比較複雜。
<details>
<summary>details</summary>
```c
int xorlist_add(xor_list_t *list, xor_node_t *n)
{
xor_node_t *real_next;
if (!n)
return ENOMEM;
xor_node_t *real_prev = &list->head;
xor_node_t *node = real_prev->cmp;
if (node == &list->tail)
real_next = &list->tail;
else
real_next = node;
real_prev->cmp = n;
n->cmp = XOR_COMP(real_prev, real_next);
real_next->cmp = XOR_COMP(n, XOR_COMP(real_prev, real_next->cmp));
list->count++;
return 0;
}
```
</details>
---