contributed by < YOYOPIG
>
linux2021
Linux Kubuntu 20.04 LTS
Could not connect to tw.archive.ubuntu.com:80 (2001:e10:2000:240:e643:4bff:fee8:a63c). - connect (111: Connection refused) Could not connect to tw.archive.ubuntu.com:80 (140.110.240.80). - connect (111: Connection refused)
之前也曾遇過一次,但是忘記怎麼解決的了。這次重新爬文,發現是因為最近 Ubuntu 在台灣的 server 不太穩定,可以嘗試更換 mirror 即可。
仔細研究要求,我們需要用單向的 linked list 實作一個 queue。同時,要可以支援
目前 queue 結構只包含了一個指向 head 的 pointer。雖然只靠這個 pointer 就足以處理上面的需求,但在插入尾部、查詢大小等操作時,都需要 traverse 過一次,需要 O(n) 的時間。為了達到 constant time,我們可以加入一個指向最後一個 node 的 pointer,以及一個紀錄大小的變數。
這裡要注意,和一些用 class 包住 data structure 的語言不同,如果我們只 free 掉 queue,並不會把整個 queue 裡的所有資源釋出。仔細看我們定義的 queue,其實只是由兩個 pointer 及一個 int 組成,並不包含 new 的過程中 malloc 出來的 node。因此,對 queue 使用 free 只會 free 掉紀錄頭尾的兩個 pointer 及記錄大小的 size,同時也無法再存取中間的 nodes,造成 memory leak。這裡最適合的作法是先一個一個的 free 掉中間的 node,最後再 free 掉我們的 queue。
插入資料基本上就是 malloc 出一個 list element,然後插入頭/尾。由於我們都有指標指向 queue 原本的頭尾了,因此 insertion 的時間複雜度都是 O(n)
小問題
在第一行的判斷式中,原本是寫成if (q == NULL || q->size==0)
,經老師建議改成if (!q || !q->size)
。可以理解在改完之後效率較高,!q
也不難理解是q不存在的意思。然而,這裡不太確定!q->size
的寫法,是否會較容易造成後續維護的問題? 似乎沒有q->size==0
來的直觀,如果是在其他有 bool 型態的語言,變數名稱又不像 size 這麼直觀的話,或許可能會搞混 data type?
之後可以查一下是否有能維持原寫法,但透過 compiler 在 compile time 自動把== 0
判斷式優化成!
的方式 (to be done…)
這裡我們實作 merge sort,細節寫在 sort.c 中,或許未來可以根據資料類型支援其他 sorting algorithm
這裡我們透過快慢雙指針找到中點,分割後做 merge sort
old version Reference
發現用 Recursive 的型式,面對大測資 trace-15 (1000000 * 2 nodes)無法跑過
考慮的解法如下:
雖然 time complexity 不變,但透過減少 recursive call,成功通過所有測試
既然 merge sort 已經是 comparison sort 系列演算法時間複雜度最低的幾種之一了,要進一步優化,顯然得用其他的方法。由於我們資料的 value 都是字串,而字串的比對是屬於一位一位分別比較的,因此這裡嘗試使用概念相似的 radix sort + counting sort 降低時間複雜度至 O(d * n)。
Reference - MikeCAT's c++ implementation
首先,我們先找出 queue 中所有元素最長字串的長度。接著,根據 radix sort 的精神,一位一位的進行排序
既然只比較每個字串的其中一個 character ,我們可以用時間複雜度最低的 counting sort 來達成。
遺憾的是,最後 Radix sort 並未帶來更好的效能,面對最大的測試資料 trace-15 依然不能在時間內跑完。為了檢測執行時間的實際情況,這裡使用 trace-16 來當作 benchmark,分別執行 merge sort 以及 radix sort。
在 trace-16 中,總共執行了六次 q_sort(),最後結果如下:
Merge sort
Radix sort
可以看到,雖然 Radix sort 理論上時間複雜度較低,但是實際執行起來花了更多的時間。另外,運用相似的想法也試著實作了利用 trie 結構優化的版本,但依然無法通過大測資 trace-15。根據這篇 stackoverflow 討論,猜想可能是因為資料中的 prefix 無法有效分開待排序資料,才造成理論實際上的差異。關於這個推論可能得再設計實驗驗證。
radix is not in words list, but should be recognized as an often-used algorithm
這裡我們使用 Massif 來視覺化 Valgrind 的分析結果。為了方便觀察,另外安裝網站上提到的圖形介面工具 massif-visualizer。
接著執行
編譯成功後,就可以監控程式執行時的記憶體使用狀況了。
程式執行之後會出現名為 massif.out.<pid>
的檔案。開啟後即可看到這次執行期間的記憶體使用量分佈狀況。
嘗試使用 Address Sanitizer 命令如下:
如作業說明所示,被噴了滿臉出現了以下 error
「噴」這個動詞適合放在這裡嗎?工程人員說話要精準
:notes: jserv
好的,下次會注意,謝謝老師指正
看起來是 echo
出現了 global-buffer-overflow 的問題。在 console.c 檔案搜尋 echo 變數,可以發現 echo 出現在十個地方。其中可以找到 echo 定義
但是在 add_param 函式中,為了符合 param_ele 的定義,使用了 int* 存取。
使用一個指向 4 bytes 大小的 int* 指向僅有 1 byte 的 bool 很明顯是一件危險的事情。這裡一開始有兩種解決的想法:
第 1 種方法效率較差,每一個 echo 變數都得多花費不必要的 3 bytes,因此優先考慮第 2 種。開始 trace add_param
可以看到,新增的 parameter 會被存起來。進一步查詢 param_ptr
的定義發現:
要支援 bool 型態的 valp,肯定得改動這個資料結構。但這時問題就出現了,以一個多人合作的專案來說, header file 中定義的資料結構常常會有大量的 dependency。擅自改動可能會造成程式其他地方的問題。當然這份程式尚且不是那麼龐大,但為了共同維護的容易,這裡就暫時先不考慮為了單一變數改動預定義的資料結構,退而求其次的改變 echo 的 type。把 echo 改成 int 後,再次執行,發生以下錯誤
可以看到,simulation
變數也有同樣的問題。這裡一樣改成 int,之後確定可以正常執行。結果如下:
事後發想:
在處理完以上兩個變數後,發現 bool 型態的變數似乎不是特例,在simulation
,verbose
,error
,echo
4 個中就佔了兩個。因此,是否有需要另外定義bool_param_ele
並採用先前提到的方法二成為值得考慮的方案。
這次作業使用到 dudect 套件檢查時間複雜度。試著閱讀原論文
筆記
TODO: Trace github code