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) 。