contributed by < yourui1017 >
yu-hsiennn可以利用 list.h 裡面的 API 來實作,以精簡呈現出來的程式碼。
allocate 的翻譯是「配置」,以區格 dispatch (分發/分配) 的翻譯。
已更改
首先用 malloc 分配 配置計憶體大小為 struct list_head 的空間,並根據 C 語言規格書[7.20.3],如果記憶體空間不足以配置,則函式應該回傳 NULL 。
If the space cannot be allocated, a null pointer is returned
相反的,如果記憶體空間足夠配置,則使用 list.h 的參考函式INIT_LIST_HEAD 函式初始化 head 並且回傳 head 。
先判斷 head 是否為 NULL ,再使用 list.h 的list_for_each_entry_safe 函式走訪每個 entry ,並考量到 value 可能會以動態記憶體宣告,因此先將 char * 變數型別存放的記憶體釋放,再釋放 entry 的記憶體。
改善漢語表達,上述說法不精確。
原本的寫法沒有考慮到 value 沒有被 malloc 的情況,因此經過考慮後,先配置 element_t 的記憶體位置後再配置 value 的記憶體位置,並將 s 字串複製給予 value 。
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
在完成 q_insert_head 和 q_remove_head 兩個函式並使用 make test 測試,發現在 trace-01-ops 出現錯誤
因此回頭檢查q_insert_head 和 q_remove_head 兩個函式,發現在q_insert_head 中的 strcpyn 的引數錯誤,引數更正如下:
使用 list.h 的 list_first_entry 找到首個 element_t ,並將該 element_t 的 value 複製給 sp ,再將該 element_t 移走。
改進你的漢語表達。
參考 你所不知道的 C 語言: linked list 和非連續記憶體 案例探討: Leetcode 2095. Delete the Middle Node of a Linked List使用快慢指標找到中間節點,再根據此節點找到 element_t 並釋放該 element_t 和 value的記憶體位置。
原始版本的 mergeTwoLists 程式碼如下:
但因為再跑 make test 時,一直解決不了在trace-03-ops出現的報錯如下:
由於多次的檢查都認為程式碼的邏輯是正確的,因此參考 Risheng1128 Hackmd ,發現該同學的程式碼中有 strcmp 語法 ,才得知在 make test 時輸入的會是字串而不是數字,因而針對程式碼進行更正。
strcmp 不是語法 (syntax),謹慎用詞!
更正的部份如下:
原始的程式碼如下:
但因為再跑 make test 時,發現在trace-06-ops出現的報錯如下:
經過檢查發現是 q_reverseK 和 q_descend 函式的邏輯錯誤。
釐清整體的架構後,更正 q_reverseK 函式的指標使用方法;更正 q_descend 函式中最小值,修正邏輯錯誤。
更正的部份如下:
q_reverseK
q_descend
本篇論文測量執行時間的方式是對執行時間進行洩漏偵測測試。
步驟一:測量執行時間
leakage detection 進行測試。cycle counter 計算實際的執行時間。步驟二:後處理
threshold 的測量刪除。步驟三:靜態測試
null hypothesis: "the two timing distributions are equal" 。如果檢測失敗代表執行時間不是常數時間。Non-parametric tests 針對這些未知分布做更穩健的測試。有鑑於 trace-17-ops 一直報錯,因此追蹤 make test 給定的命令檔如下:
可以發現它會將 simulation flag 設成1,並執行 it 等等指令。
因此再追蹤到 qtest.c 的 queue_insert 中,找到 qtest.c 會呼叫 is_insert_tail_const() ,最後追蹤到 fixture.c 會根據 論文 Dude, is my code constant time? 進行執行時間為 constant time 的驗證。
在作業要求有提到 oreparaz/dudect 的程式碼具備 percentile 的處理,但在 lab0-c 則無,因此比較兩者差異。
在比較完 fixture.c 和 oreparaz/dudect 可以發現 lab0-c 並沒有考慮到 論文 Dude, is my code constant time? 中,步驟二後處理的 CROP ,因此將 oreparaz/dudect中的 percentile 在 fixture 中實做。
其中要特別注意到 prepare_percentiles 只能夠做一次。
根據 oreparaz/dudect 在 prepare_percentiles 的註解中,可以知道 prepare_percentiles 會給定要被去除的 threshole ,因此必須保持 threshold 固定。
最後 doit 就會如以下的程式碼:
參考 Risheng1128 將list_sort 引入 lab0-c。
在 Makefile 中新增 compare 執行 traces/trace-sort.cmd
traces/trace-sort.cmd 的內容如下:
可藉由調整 Rand 調整資料的數量。
輸入指令
結果如下:
| 資料總數 | q_sort() 測試1(s) | q_sort() 測試2(s) | q_sort() 測試3(s) | q_sort() 測試4(s) | q_sort() 測試5(s) |
|---|---|---|---|---|---|
| 100000 | 0.08 | 0.076 | 0.078 | 0.075 | 0.076 |
| 300000 | 0.302 | 0.309 | 0.316 | 0.309 | 0.313 |
| 500000 | 0.614 | 0.585 | 0.578 | 0.576 | 0.585 |
| 資料總數 | list_sort() 測試1(s) | list_sort() 測試2(s) | list_sort() 測試3(s) | list_sort() 測試4(s) | list_sort() 測試5(s) |
|---|---|---|---|---|---|
| 100000 | 0.052 | 0.053 | 0.053 | 0.056 | 0.052 |
| 300000 | 0.205 | 0.207 | 0.206 | 0.202 | 0.204 |
| 500000 | 0.383 | 0.372 | 0.374 | 0.375 | 0.374 |
| 資料總數 | q_sort() 平均(s) | list_sort() 平均(s) | 效能比較(%) |
|---|---|---|---|
| 100000 | 0.077 | 0.053 | 31.16% |
| 300000 | 0.310 | 0.205 | 33.87% |
| 500000 | 0.588 | 0.375 | 36.22% |
可以看到 list_sort() 的效能比起 q_sort() 還要好,且也可以觀察到隨著資料總數大小增加,效能的差距也變得愈加明顯。
接下來使用 Linux 效能分析工具: Perf 比較效能細項的部份。
使用更改kernel權限
輸入指令
讓它根據 trace-sort.cmd (此處的 Rand 為500000)針對 cache-misses 、 cache-references 、 instructions 、 cycles 做10次測試並且輸出平均值。
結果如下:
| q_sort | list_sort | |
|---|---|---|
| cache-misses | 56,154,930 | 45,669,350 |
| cache-references | 102,054,812 | 79,353,435 |
| instructions | 2,414,610,171 | 2,394,503,043 |
| cycles | 4,502,998,337 | 3,601,189,041 |
| insn per cycle | 0.54 | 0.66 |
可以看到不管是 cache-misses 、 cache-references 、 instructions 還是 cycles , list_sort() 都是優於 q_sort()。
在 Linux 核心專題: 改進 lib/list_sort.c 中提及可以使用 bottom-up 的方法改善 cache-thrshing 的問題,以加快速度。
因為不懂 top-down 和 bottom-up 的實作差別,所以參考了 動態規劃(Dynamic Programming),裡面提及 top-down 和 bottom-up 的差別,且通常 top-down 會使用遞迴來實作,而 bottom-up 通常使用迭代實作,因此我又開始疑惑,所以遞迴的速度一定會比迭代還要慢嗎?
又參考了 關於遞迴加快速度的迷思?才終於對遞迴和迭代有了一點點的概念。
接著,參考 Merge Sort 與它的變化 使用了迭代的方式完成 Merge sort,並且發現,因為程式碼實作的關係,遞迴版的 Merge sort 比起迭代版的 Merge sort有更多的 Cache miss,導致速度變慢。
參考自 csotaku0926 同學。
bottom-up 的過程就是一直合併,cache 元素參照的機會更大。
top-down 涉及 parition,這麼做會導致元素不斷從 cache 進出,而導致 cache thrashing 的問題。
TODO:嘗試實作 Timsort 演算法,引入 lab0-c 與 list_sort 進行比較。
Timsort 內容請看 2024q1 Homework2 (quiz1+2) 。