# 2024q1 Homework1 (lab0) contributed by < [Dainel-0224](https://github.com/Daniel-0224/lab0-c) > ### Reviewed by `david965154` 1. > 這裡採取遞迴 `merge_sort` 來實作 `q_sort` 應該修改為 : 「這裡採取 `merge sort` 的遞迴版本來實作 `q_sort` 」較適當,加了底線會讓人誤以為你實作了這個函式,也應提到使用了下方函式 `q_merge_two` 完成 merge 的功能。 2. > 一開始想不到一個從頭走到尾的簡潔寫法,但在經過 [HotMercury](https://github.com/HotMercury/lab0-c) 及 [devarajabc](https://github.com/devarajabc/lab0-c) 的提醒,採取從尾走到頭的方式 可以解釋一下為什麼從頭走到尾不行,但可以從尾走到頭,例如 : 因為 `q_descend` 題目要求為 : 只要在此節點後方的節點有存在比此節點更大之成員 `value` 則刪去,因此等同於尋找佇列後方的嚴格遞減佇列,直接從後方找最大成員 `value` 並向前比對,只需逐一走訪一次就可以完成實作。 > 「從尾走到頭」不精準,因為在環狀雙向鏈結串列中,已是首尾相接,於是尾端必定會緊接著開頭。真正的關鍵是運用 prev 和 next 來存取節點的方向。 :notes: jserv ## 開發環境 ```shell $ 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: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 16 On-line CPU(s) list: 0-15 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz CPU family: 6 Model: 165 Thread(s) per core: 2 Core(s) per socket: 8 Socket(s): 1 Stepping: 5 CPU max MHz: 5100.0000 CPU min MHz: 800.0000 BogoMIPS: 7599.80 ``` ## 指定的佇列操作 ### `q_new` 用 `malloc `來配置 `head` 所需記憶體,並使用`INIT_LIST_HEAD`初始化`head`。要注意的是`malloc`不一定會成功,在配置記憶體失敗時,得到指標值會是 `NULL`,因此需要在配置後檢查`head`。 :::danger 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利) ::: ### `q_free` 藉由 `container_of` 巨集找到 `element_t` 結構的起始地址,`offsetof(type, member)` 可得知 `member` 在 `type` 這個結構體位移量。將絕對位址 `(char ) pmember` 減去 `offset` ,可得到結構體的起始位址。`head` 是個特例,無法藉由 `containerof` 巨集來找到對應的節點,因為後者並未嵌入到任何結構體之中,其存在是為了找到 `list` 整體。 ### `q_insert_head & q_insert_tail` `strdup` 有使用 `malloc` 來配置字串存放的記憶體空間,空間配置失敗時要釋放記憶體,這裡要釋放整個 `element_t`。 而 `q_insert_head` 、`q_insert_tail` 分別使用 `list_add` 和 `list_add_tail` 來串接節點。 ### `q_remove_head` 利用 `list_first_entry` 找到第一個 `element` ,並用 `list_del` 將其與佇列斷開連接,再利用 `strncpy` 複製字串。 ```c element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if(!head || list_empty(head)) return NULL; element_t *first = list_first_entry(head, element_t, list); list_del(&first->list); if(sp && first->value){ strncpy(sp, first->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return first; } ``` ### `q_remove_tail` 一開始我只將 `list_first_entry` 改為 `list_last_entry` ,但在參考< [alanjian](https://hackmd.io/@alanjian85/lab0-2023) >的想法後發現,這兩題程式碼相似度高,其實可以重複使用。 ```c element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if(!head || list_empty(head)) return NULL; return q_remove_head(head->prev->prev, sp, bufsize); } ``` ### `q_size` 用 `list_for_each` 走訪每個節點,並隨之遞增 `count` 。 ### `q_delete_mid` 這裡使用了 `fast` 和 `slow` 兩個指標,當 `fast` 走到序列結尾時,`slow` 走到序列一半,再將這個 `element` 刪除即可。 ### `q_reverse` 使用 `list_for_each_safe` 來走訪序列是因為需要 `safe` 來紀錄當前處理節點的下個節點,在使用 `list_move` 將 `node` 移至首位。 ### `q_reverseK` 我的想法是在走訪了 k 個節點後,將這 k 個節點利用 `list_cut_position` 從原序列截斷後再利用 `q_reverse` 反轉,最後再用 `q_splice` 接回原序列。我一開始想利用 `q_new` 創建一個節點來連接截斷的序列,然後發現在這個函式中不能使用 `malloc` ,並經過同學的題點改為使用 `LIST_HEAD` 。 ``` $ ./qtest <test.cmd FATAL ERROR: Calls to malloc disallowed FATAL Error. Exiting ``` ```diff list_for_each_safe (node, safe, head) { if (count != 1) { count--; } else { - tmp = q_new(); + LIST_HEAD(tmp); count = k; list_cut_position(tmp, cut, node); q_reverse(tmp); list_splice(tmp, cut); cut = safe->prev; } } ``` ### `q_swap` `q_swap` 可以想成 `q_reverseK` 而 `k = 2` 。 ### `q_descend & q_ascend` :::danger 明確標注同學的 GitHub 名稱。 ::: 一開始想不到一個從頭走到尾的簡潔寫法,但在經過 [HotMercury](https://github.com/HotMercury/lab0-c) 及 [devarajabc](https://github.com/devarajabc/lab0-c) 的提醒,採取從尾走到頭的方式,在過程中會判斷目前節點與前一個節點的大小,如 `descend` 前一個節點小於目前節點,則需要將前一個節點刪除,相反則移動目前節點到前一個節點。 ### `q_sort` 這裡採取遞迴 `merge sort` 來實作 `q_sort` ,並且使用 `q_merge_sort` 來達成兩串列合併,一開始一樣以快慢兩個指標來找出中間點,再利用 `list_cut_position` 將串列分成兩串再進行一次 `q_sort`。 :::danger 你如何確保目前的測試程式已涵蓋排序演算法的最差狀況? ::: ### `q_merge_two` 這裡逐一比對兩個串列的節點的大小,合併的方式是從輸入的二個串列中,根據 `descend` 參數取較小或較大的,放到一個暫存串列 `tmp` 中。 一開始我是以 `list_add_tail` 這個函式來將節點串接上新的串列,但在執行時遇到無窮迴圈。發現是這個函式沒有將節點從原本串列移除,最後改用 `list_move_tail` 將問題解決。 ```diff while (!list_empty(left) && !list_empty(right)) { element_t *l = list_entry(left->next, element_t, list); element_t *r = list_entry(right->next, element_t, list); if (strcmp(l->value, r->value) >= 0) { - list_add_tail(&l->list, &tmp); + list_move_tail(&l->list, &tmp); } else { - list_add_tail(&l->list, &tmp); + list_move_tail(&r->list, &tmp); } } ``` ### `q_delete_dup` 我的想法是用兩個指標指比對兩個節點是否相同,相同則刪除。原本我使用 `list_for_each_entry_safe` 來做比對,但是在 `strcmp(cur->value, post->value)` `post` 指向 `head` 時會造成 `Segmentation fault occurred. You dereferenced a NULL or invalid pointerAborted` 。 ```diff bool q_delete_dup(struct list_head *head) { if(!head) return false; bool flag = false; element_t *post, *cur; list_for_each_entry_safe(cur, post, head, list) { - if(!strcmp(cur->value, post->value)) { + if(&post->list != head && !strcmp(cur->value, post->value)) { flag = true; list_del(&cur->list); q_release_element(cur); } else { if(flag) { list_del(&cur->list); q_release_element(cur); flag = false; } } } return true; } ``` ### `q_merge` 利用兩個指標來走訪 `chain` ,每次迭代合併兩個 `q_contex_t` ,因為這裡使用 `q_merge_two` 來做合併,因此 `first` 串列會越來越長,使得走訪成本越來越高。 :::danger 需要有更多討論。 ::: --- ## 論文研讀〈[Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)〉 ### Step1. Measure execution time **a ) class definition** 建立兩種類型的資料,並重複測量其作為輸入資料時的執行時間。 固定資料:這包括了特殊的極端情況。(such as low-weight input for arithmetic operations) 隨機資料:隨機生成的資料,以模擬實際情況下的不確定性和變化性。 **b ) cycle counters** 使用 CPU 的 cycle clock, 可準確取得執行時間。 **c ) environment conditions** 最大限度地減少環境變化對結果的影響,每次測量都對應一個隨機選擇的類別 ### Step2. Post-processing 在統計分析之前,我們對每個測量值進行了一些處理。 **Cropping** :由於測量過程中可能會受到作業系統或其他活動的干擾,我們需要對測量值進行裁剪,去除受外部干擾的部分,以獲得準確的執行時間。 **Higher-order preprocessing** : 根據所應用的統計測試,去應用像是 centered product mimicking higher-order DPA attacks 預處理得到更好的數據。 ### Step3. Apply statistical test 採用統計檢定方法來試圖反駁「兩個時序分佈相等」的原假設。成功反駁這個假設將證明該假設錯誤,從而表明程式執行時間不是常數。 * t-test:為 dudect 所使用的 Welch's t-test。 ## Valgrind 與 Address Sanitizer 這部份是因為我在 `q_free` 少寫了一行 `free(head)` 所導致的。 ``` $ valgrind -q --leak-check=full ./qtest <tttt.cmd ``` ``` l = [] l = [b] Freeing queue ERROR: Freed queue, but 1 blocks are still allocated ==20977== 56 bytes in 1 blocks are still reachable in loss record 1 of 1 ==20977== at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==20977== by 0x10F327: test_malloc (harness.c:133) ==20977== by 0x10F72C: q_new (queue.c:17) ==20977== by 0x10CDE3: do_new (qtest.c:155) ==20977== by 0x10E011: interpret_cmda (console.c:181) ==20977== by 0x10E5C6: interpret_cmd (console.c:201) ==20977== by 0x10F230: run_console (console.c:691) ==20977== by 0x10D403: main (qtest.c:1258) ==20977== ``` 更改完程式碼後執行 `$valgrind --tool=massif ./qtest <tttt.cmd` 得到 `massif.out.19175` 檔案。再利用以下指令可視化程式執行時期的記憶體行為。 ``` $ ms_print massif.out.19175 ``` ``` -------------------------------------------------------------------------------- Command: ./qtest Massif arguments: (none) ms_print arguments: massif.out.19175 -------------------------------------------------------------------------------- KB 20.45^ ::: | :::#: | : #: | : #: | : #: : | @ #: @ | :@ #: @ | :@ #: @ | :@ #: @ | :@ #: @ | :@ #: @ | :@ #: @ | :@ #: @ | :@ #: @ | :@@@ #: @ | :@@@ #: @@ | :@@@ #: @@ | :@@@ #: @@ | :@@@ #: @@ | @@:@@@ #: @@ 0 +----------------------------------------------------------------------->ki 0 313.0 ``` ``` $massif-visualizer massif.out.19175 ``` ![Screenshot from 2024-03-04 23-34-46](https://hackmd.io/_uploads/HJjC9Dmaa.png) ## 實作 shuffle 根據〈[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#Fisher%E2%80%93Yates-shuffle)〉中提到的 `Fisher–Yates shuffle` 演算法來實作 `shuffle` 。 * 假設初始的序列如下: | 隨機數範圍 | 選擇隨機數 | 原始序列 | | ---------- | ---------- |:--------- | | | | 1 2 3 4 5 | * 從 1~5 間隨機選一數字,例如這裡的 `3`,將原始序列從左開始數的第 3 個數字和最後 1 個數字對調 | 隨機數範圍 | 選擇隨機數 | 更新後序列 | | ---------- | ---------- |:--------- | | 1-5 | 3 | 1 2 5 4 3 | * 從 1~4 間隨機選一數字,以這裡為例是 2,所以把原始序列從左開始數的第 2 個數字和倒數第 2 個數字交換 | 隨機數範圍 | 選擇隨機數 | 更新後序列 | | ---------- | ---------- | ------------- | | 1-4 | 2 | 1 4 5 2 3 | * 重複這個流程,直到隨機數範圍為 0 ### 實作 1. 模仿 do_reverse 在 qtest.c 中產生 do_shuffle。 2. 在 queue.c 中實作 q_shuffle ``` Expectation: 41666 Observation: {'1234': 41886, '1243': 41890, '1324': 41603, '1342': 41557, '1423': 41721, '1432': 41872, '2134': 41610, '2143': 41395, '2314': 41707, '2341': 41679, '2413': 41962, '2431': 41945, '3124': 41790, '3142': 41464, '3214': 41562, '3241': 41347, '3412': 41504, '3421': 41982, '4123': 41643, '4132': 41867, '4213': 41370, '4231': 41482, '4312': 41600, '4321': 41562} chi square sum: 21.029184466951474 ``` ![Figure_1](https://hackmd.io/_uploads/H15rcPNpT.png)