contributed by < 56han
>
allenliao666
q_ascend
的內文及註解有誤,應該是若右側節點小於當前節點才需標記當前節點為需要刪除。q_ascend
和 q_descend
有不需雙層迴圈即可完成的實作方法,可以嘗試看看。Ackerman666
q_delete_mid
在雙向佇列中,可從頭尾開始往中間找尋中間點
如此只需走訪一次串列就行 可參考 YangYeh-PD
的解說
q_new
定義一個函式 q_new
,用於動態配置,並初始化一個空的雙向鏈結串列節點,如果配置失敗則返回 NULL
。
q_free
原本寫法如下,但寫完 q_insert_head
之後,反而分數變少。
我認為是配置給 element_t
結構體中 value
欄位的記憶體沒有被釋放。此外,直接釋放 struct list_head
結構體的 pointer 也可能破壞 list 的結構,因為它沒有按照 list 的邏輯結構來逐一走訪每個節點,並釋放每個元素。
避免過多的中英文混用,已有明確翻譯詞彙者,例如「鏈結串列」(linked list) 和「佇列」(queue),就使用該中文詞彙,英文則留給變數名稱、人名,或者缺乏通用翻譯詞彙的場景。
q_insert_head
用於在雙向鏈結串列的頭部插入一個新元素,新元素中包含了一個字串s
。如果插入成功返回 true
,否則返回 false
。
q_insert_tail
將q_insert_tail
中的差異在於插入節點的位置不同,因此只須修改最後一行。
觀摩 164253,可以在 q_insert_tail
中直接呼叫 q_insert_head
,並將第一個參數改為 head->prev
即可。
q_remove_head
Delete 與 Remove 的概念可以參考 2024 年 Linux 核心設計/實作課程作業 —— lab0 (A) 提及的內容,主要的差異為目標節點還是否存在於記憶體空間。
從雙向鏈結串列的 head 移除一個元素。將移除的元素中的字串複製到 sp
指向的緩衝區中,並確保字串以空字串終止。
q_remove_tail
將 q_remove_tail
中的差異在於插入節點的位置不同,因此改從 tail 獲取最後一個元素,並刪除最後一個元素。
參考 164253,
和 q_insert_tail
一樣 可以簡單的用 q_remove_head
完成
q_delete_mid
參考 你所不知道的 C 語言: linked list 和非連續記憶體
方法:使用快慢指標
q_delete_dup
原本誤以為題目的意思是遇到重複,只要留下一個元素,並把後面連續重複的元素都刪除。
後來依題意改成刪除所有連續重複的元素。
觀摩 96121503,自我檢討,其中:for loop
可以善用 list.h 的 list_for_each_entry_safe
完成,使程式碼更加簡潔。
q_swap
原本想法兩兩互換,後來參考 laneser
同學方法: 先移除一個節點,再加入在交換的位置上。
觀摩 yutingshih,發現可以善用 list.h 中的 list_move_tail()
撰寫。
list_move(node, head)
:將 node 移動到 head 的後面,成為鏈結串列的第一個節點。
list_move_tail(node, head)
:將 node 移動到 head 的前面,成為鏈結串列的最後一個節點。
改進你的漢語表達。
q_reverse
利用 list.h 中的list_move
,將鏈結串列中的結點一個一個往前移。
q_reverseK
逐一走訪鏈結串列,每當計數達到 k
時,就將當前到達的位置之前的部分切割出來,翻轉,然後接回原來的位置,直到逐一走訪完整個鏈結串列。
q_ascend
透過外層循環逐一走訪整個鏈結串列,對於每個節點,使用內層循環 比較其右側所有節點的值。
如果發現右側有節點的值大於當前節點的值,則標記當前節點為需要刪除。
逐一走訪完成後, list_del
將需要刪除的節點從鏈結串列中移除並 q_release_element
釋放相關資源。
程式碼註解總是用美式英語書寫!
程式碼列出是為了討論和檢討,抽離出關鍵程式碼來討論。
q_descend
和q_ascend
相同,修改判斷式即可。
q_merge_two
合併兩個雙向鏈結串列 first
和 second
。使用一個臨時鏈結串列 tmp
來存放合併後的結果。
並逐一比較 first
和 second
鏈結串列中的元素,按照元素值的升序將元素移動到 tmp
鏈結串列中。
當其中一個鏈結串列被完全移動完畢後,善用 list.h中的list_splice
,將剩餘的元素也加入到 tmp
中,並最終使用list_splice_tail_init
將 tmp
鏈結串列中的所有元素移動回 first 鏈結串列中。
q_sort
以 Divide and conquer 實作 Merge Sort 對鏈結串列進行排序。我使用的方法是先以升序作排列,最後判斷 descend 值,判斷是否需要反轉(reverse)。
你如何確保目前的測試程式已涵蓋排序演算法的最差狀況?
TODO: 思考如何避免遞迴
q_merge
處理了將多個排序好的子佇列合併成一個大佇列,並根據指定順序進行排序的任務。
這些子佇列使用 queue_contex_t
結構表示,並藉由 chain
成員鏈接在一個主要的鏈結串列 head 中。合併後的佇列給可以根據 descend
參數以升序或降序進行排序。
閱讀 The Valgrind Quick Start Guide 得知,Valgrind 工具套件提供了許多 debugging 和分析工具,可協助我的程式更快、更正確,其中最受歡迎的是 Memcheck。它可以檢測 C 和 C++ 程式中常見的許多與記憶體相關的錯誤,這些錯誤可能導致崩潰和不可預測的行為。
程式將比正常運行速度慢得多,並且使用更多記憶體。Memcheck 將發出有關其檢測到的記憶體錯誤和洩漏的訊息。
它測量程式使用了多少 heap 記憶體,測量程式堆疊的大小。此外,某些空間洩漏是傳統洩漏檢查器(例如 Memcheck )無法偵測到的。
這是因為記憶體實際上並沒有丟失,仍然有一個指向它的指標,但它沒有被使用。隨著時間過去,存在此類洩漏的程式會增加其使用的記憶體量,Massif 可以幫助識別這些洩漏。
運行 trace-017-complexity
test_const
的記憶體使用量穩定且持續增長,代表它沒有釋放記憶體。
TODO:
為何在 "Peak of 1.7 MiB at snapshot #87" 記憶體使用會達到峰值。
閱讀 Fisher–Yates shuffle 演算法之後,實作洗牌(shuffle)。
首先在 qtest.c
中 ADD_COMMAND
,在開始實做 q_shuffle
。
原本直接將 q_shuffle
實作在 queue.c
中,並在 queue.h
新增 q_shuffle
的宣告 ,git commit 之後出現錯誤訊息如下:
因此得知不能修改 queue.h
檔,於是我多寫了兩個檔案,分別是:shuffle.h
和 shuffle.c
。
commit e26bd21
commit shuffle.c
時,出現了以下錯誤
是因為 cppcheck 檢查到了非標準代碼,如果想要過濾掉某些錯誤,可以加上 cppcheck-suppress aaaa
。因此我加入註解 // cppcheck-suppress nullPointer
以忽略警告。
shuffle
從圖表來看 shuffle 的頻率大致符合 Uniform distribution。
Dudect 是一種用於評估軟體實作是否符合常數時間執行的工具,對於密碼學實作的安全性非常重要。常數時間執行能夠防範定時攻擊 (timing attack),這是一種利用密碼學運算的執行時間差異,來推測密鑰或機密資訊的側錯分析攻擊手法。
它的貢獻在於以很小的實作成本,提供高度的安全性檢測能力。使用的 t-test 是用於檢測算法執行時間是否存在統計學上的顯著差異,這對於偵測側通道攻擊非常重要。
dudect.h 過程就是一個迭代的統計檢測,透過不斷收集新的測量數據、更新統計數據、設置合理的閾值過濾極端值,最終得出被測試函數是否存在 Timing leakage 風險。
其中 prepare_percentiles()
函式,是先將執行時間進行排序,接著從排序後的 ctx->exec_times
執行時間資料中給定百分位數對應的值。
此函式目的在於計算出執行時間閾值,用於後續過濾極端值。透過設置不同的閾值,可以有選擇地過濾掉測量值中的極值,從而獲得更加準確的統計結果。
另外 update_statistics
中以捨棄前 10 筆測量,以避免一開始測量的不穩定性。difference
儲存每次迭代的執行時間差異,對每個有效的 difference
執行 t-test,檢測算法執行時間是否存在統計學上的顯著差異。
迴圈走訪多個剪裁閾值,只對那些低於特定閾值的 difference
執行 t-test。這可以幫助識別在不同的執行時間閾值下,算法的行為是否存在顯著的統計差異。
lab0-c
加入具備 percentile
的處理commit e91a53f
在 dudect/fixture.c
中,我發現 doit
這個函式實作與 dudect.h
主要實作功能類似,但沒有實作 percentile
這個部份,造成極端值沒有被去除,導致判斷結果出錯。
將 dudect.h 處理 percentile
的部分加入 lab0-c
的 fixture.c
後便可以正確分析 insert_head
, insert_tail
, remove_head
, remove_tail
的執行時間。
lib/list_sort.c
安裝 perf 及設定系統權限,預設會使 perf 權限不足:
lib/list_sort.c
將 lib/list_sort.c 和 linux/list_sort.h 引入本專案中
隨機產生 500000 個字串加入到鏈結串列中,使用不同的 perf 效能分析工具分析兩種不同的排序方式效能上的差異。
traces/trace-sort.cmd:
執行以下指令,以比較執行時間
q_sort 執行時間表:
測試次數 | 執行時間(秒) |
---|---|
1 | 0.9123 |
2 | 0.9287 |
3 | 0.9295 |
4 | 0.9474 |
5 | 0.9541 |
linux 的 list_sort 執行時間表:
測試次數 | 執行時間(秒) |
---|---|
1 | 0.45526 |
2 | 0.4762 |
3 | 0.46526 |
4 | 0.45756 |
5 | 0.45429 |
之後執行 make test
,沒有出現任何記憶體相關錯誤訊息。
將 jserv/ttt 專案的 zobrist.[ch]
會用到 hlist.h
相關的程式碼抽出,成為 hlist.h
,並確保後者可與 lab0-c
既有的 list.h
並存。
commit 53ad444
修改 qtest
程式,使其新增 ttt
命令,其作用是執行與人類對弈的井字遊戲,從 jserv/ttt 的 main.c
檔案中提取與遊戲相關的程式碼至專案中的 console.c
。當輸入指令 ttt
後,將繼續執行原始 main.c
中的內容,以啟動井字遊戲。
commit 4876b95
此方法依賴平衡探索和利用的智慧搜尋樹。執行隨機採樣並儲存操作統計數據,以便在每次後續迭代中做出更明智的選擇。
每一個節點的內容代表勝利次數/遊戲次數