or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
2023q1 Homework1 (lab0)
contributed by < chiangkd >
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →作業要求
Request followed 2023 年 Linux 核心設計/實作課程作業 —— lab0
queue.[ch]
以完成make test
自動評分系統的所有項目qtest
實作的記憶體錯誤,搭配 Massif 視覺化qtest
提供的web
命令可以搭配上述佇列使用shuffle
, 參考 Fisher–Yates shuffleqtest
中執行option entropy 1
並搭配ih RAND 10
,觀察亂數字串分佈開發環境
改進
queue.c
結構體
list_head
為 doubly-linked list 的 node 或 headelement_t
為 linked list 的 elementelement_t
這個結構體,除了要用來找尋list
整體的原因,透過嵌入list_head
給除了Head
可以搭配container_or
巨集也可得知queue_context_t
為 chain of queues在多個 queue 之間的操作,如
do_queue
是透過chain
來連接起來的,也因此在q_merge
function 中給定的參數為list_head *head
,但查看do_merge
之後才會看到這裡傳入的是queue_chain_t
的chain
,也是連接各個 queue 的 linked list。做個驗證,在
q_merge
中來測試並呼叫merge
command,函式中暫時先撰寫透過傳入的chain
找其所屬element_t
的 valueelement_t
的value
與其複製程式碼,你應揣摩背後的設計考量,特別是你的推理和驗證。
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →題外話,在化 graphviz 的時候想要虛線功能,但不知道關鍵字是什麼 (加粗為

bold
,直覺認為是dot
),問了一下 chatGPT 之後它告訴我是dotted
q_new
改為更精簡的敘述:
修正程式碼註解用語。
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →q_free
q_insert_head
能否避免呼叫
memcpy
並精簡程式碼?- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →發現在
harness.c
中已有定義strdup
(test_strdup
),此函式回傳輸入 strings
的指標,並回傳一個具有同樣內容的指標,即複製字串。透過此函式可以直接取代掉原實作方法中的計算長度、分配空間、記憶體複製操作
q_insert_tail
和
q_insert_head
思路相同, 僅修改head
和tail
的差別既然
q_insert_tail
和q_insert_head
存在相似的流程,能否用q_insert_head
來實作q_insert_tail
呢?- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →q_insert_head
以及q_insert_tail
差異僅在新增節點於給定head
的後一個位置 (next
所指方向),以及前一個位置 (prev
所指方向),故可以透過q_insert_head
來實作q_insert_tail
翻譯成白話文的意思就是,在
head
的前面插入節點等效於在head
的前一個節點的後面插入節點。q_remove_head
container_of
這個巨集透過給定成員 (member) 的記憶體地址、結構體的型態,以及成員的名稱, 傳回該結構體的起始地址上述雖然可以成功 remove head, 但是產生錯誤訊息
查看
qtest.c
中do_remove
的實作implementation 應翻譯為「實作」,注意「作」和「做」的落差」。
參見 資訊科技詞彙翻譯 的 "implement" 項目。
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →removes[0] = '\0'
會報此錯誤, 且removes
在qtest.c
已經malloc
如果在q_remove
中再次malloc
一次的話 sp 的地址會被覆蓋掉, 即原先removes
的地址其實並沒有存東西, 所以這裡的sp
不該malloc
在佇列為空時繼續使用命令
rh
會造成 segmentation fault, 當佇列為空時僅有Head
一個點, 沒辦法通過container_of
找到對應節點加入
list_empty
來判斷該鏈結串列是不是 empty 以及外部removes
是否有malloc
成功這裡的
container_of
可以用list_first_entry
巨集代替, 增加程式可讀性 (defined inlist.h
)list_last_entry
取代container_of
q_remove_tail
和
q_remove_head
思路相同, 僅修改head
和tail
的差別q_size
走訪整個鏈結串列以計算長度
TODO: 善用既有的巨集,撰寫出更精簡的程式碼
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →其中
list_for_ecah
定義為q_delete_mid
本例為 circular doubly-linked list,你應該實作更少記憶體操作的程式碼。
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →原先版本使用快慢指標,快指標會走訪整個 list 一圈,慢指標會走一半的 list 找到中間點,總共會走大概 1.5 倍長度的 list,參考 freshLiver 過往作業使用到兩個指標前後從佇列頭尾迭代,透過交會點/即將交會點來判斷佇列中間點
在這邊使用 indirect pointer 看起來沒有特別的優勢,反而在過程多了
*indir
這一個記憶體操作對照編譯器輸出的組合語言和
perf
來確認你的觀點,否則這個「看來」流於武斷。注意編譯器最佳化的影響- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →且如此運算過後
n
會正好指向要移除之節點,不需要在額外設定指向要刪除的節點進行操作,編譯器輸出的組合語言
用
objdump -d queue.o
查看編譯器輸出的組合語言結果上述的兩個版本編譯器編出來的組合語言竟然是一樣的,查看
makefile
發現編譯器優化等級設定在-O1
,如果禁用優化改成-O0
編譯出來的組合語言才會有差使用 perf 確認上述猜測
定義一個測試案例
trace-demid.cmd
輸入命令
perf stat
可以測量單個程序運行時間內的性能係數--repeat 5
重複 5 次-e
後面接著 PMU 的事件,可以用perf list
查看所有事件結果如下
Iteration process
以 2095. Delete the Middle Node of a Linked List 舉例
Old version
利用快慢指標 (Floyd's tortoise and hare) 找中間點, 因為在本次作業中已經確保快慢指標 (或龜兔指標) 已經都在環中, 所以設定快指標移動速度為慢指標兩倍, 當快指標繞完一圈時慢指標指向中間點(有
Head
節點則差一個節點)(*indir)->next
直接 release 掉的話會造成(*indir)->next
指向NULL
這不是我們想要的, 這裡我先用list_del((*indir)->next)
將(*indir)
和(*indir)->next->next
linked 起來,在將原先的(*indir)->next
release 掉Iteration Process
以 2095. Delete the Middle Node of a Linked List 實際演算一次
(*indir)->next
節點q_delete_dup
首先思考使用
list_for_each_entry_safe
實作,根據描述會走訪整個 list 的 entries (
typeof(entry)
) 並允許刪除當前的節點執行
list_for_each_entry_safe
可以走訪整個 list 但是需要注意在走訪完時n
會指向head
,且head
是無法透過container_of
找到節點的,因為它沒有嵌入到結構體中,這時如果對n
取contain_of
會拿到NULL
如果再做取值n->value
會報錯成為無效的記憶體操作。故在第一個
if
條件使用c->list.next != head
來判斷是否來到最後的節點 (雖然list_for_each_entry_safe
本身就有做),避免在strcmp(c->value, n->value) == 0
時n->value
對NULL
指標取值。TODO: 減少記憶體操作的數量
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →q_swap
交換佇列中鄰近的節點,將兩個鄰近的節點中的第一個節點移動至以令一個當作
head
節點的開頭 (也就是說會放在另一個節點的next
),移動節點可分為兩步驟(刪除以及插入),對應到list_move
中定義的兩步驟list_del
以及list_add
q_reverse
一開始原本是考慮到沒有刪除節點的問題所以使用
list_for_each
來走訪 list ,但是在 reverse 的過程中的node
會把next
和prev
做交換,會讓原本定義的巨集出現錯誤,導致無法順利走訪。改用
list_for_each_safe
在迭代過程中
safe
指標會始終維持在node
的下一個節點(沒有 reverse 的),使用node = safe
以及safe = node->next
確保迭代過程node
不會往回走。q_reverseK
此
函數函式需要將 list 中的 k 個節點進行 reverse,直覺來說會利用到上方已經實作完成的q_reverse
,為此在使用時符合q_reverse
參數需求,傳入該 list 的head
,故使用list_cut_position
對特定長度的 list 進行分割,並在分割下來的 list 頭部新增iter
作為該 list 的head
供q_reverse
使用注意用語,參見 資訊科技詞彙翻譯。上述 "reverse" 應有對應的漢語描述
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →q_sort
實作 merge sort,先將環狀結構斷開後,傳入
head->next
進去merge_recur
merge_two_list
TODO: 撰寫更精簡的程式碼,縮減分支
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →merge_recur
避免使用含糊的「中點」,應有對應的定義及說明。
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →q_sort
head->next
進行 merge sort 可以保留原本的head
,在排序過後在進行prev
以及circular
的修補q_descend
此函數會將右邊存在有嚴格大於的節點的節點給移除 (remove)
如果順著 list 方向進行走訪 (以
next
指標迭代),無法確定迭代的過程中會不會遇到嚴格大於的節點(如果遇到則走放過的節點都要移除),故這邊我以反方向進行迭代(以prev
指標迭代),找尋下一個節點值只要和當前節點做比較,若下一個節點小於當前節點的話則移除。執行測試案例的時候發現
trace-06
過不了,發現是descend
後被消除的 block 沒有被清掉,一開始看到函式敘述用remove
時以為不用刪除但它也沒有存到像
q_remove
系列的sp
中,由q_test
負責刪除,故修正在函式內進行記憶體釋放q_merge
先看一下
do_merge
要我們做什麼且在
do_merge
中會負責把被 merge 掉的 queue 空間free
掉,接著再檢查是否真的有按照 ascending order 來排序實作過程中利用在 merge sort 已經寫好的
merge_two_list
,不過須注意的是這邊我的merge_two_list
傳入值為兩個沒有head
的 list,也就是每一個節點其對應到的 element 都有數值,且不是環狀 linked list ,實做過程將兩個已經 sorted 好的 list 傳入 function 並回傳將兩個 list merged 完成的 list 的第一個節點地址,此時可以把 merged 好的 list 交給主要的 context (迭代中的) 的 list head (其 element 沒有值的那個)完成之後會有一個 merged 好的 list,接著將環狀的結構復原 (
prev
被各個佇列的 merge 打亂了,僅有next
為正常順序)最後把處理好的 list 透過
list_splice
交給主要 chainhead
所屬的element
研讀論文並改進 dudect
此節根據〈Dude, is my code constant time?〉修正測試案例
trace-17-complexity
檢測程式碼不會常數執行時間的問題,在trace-17-complexity
測試案例中會將option simulation
打開,並執行 insert head (ih
)、insert tail (it
)、remove head (rh
)、remove tail (rt
) 指令,而在qtest.c
中相對應的do_it
、do_ih
、do_remove
都有對應simutlation
開啟狀態下的行為。解釋 simulation 模式
首先查看
do_ih
在
simulation
下,對應到三種結果是否為 constant time 由
is_insert_head_const()
函式回傳判斷,is_##x##_const
系列函式由前置處理器 concatenation 處理,其中insert_head
,insert_tail
,remove_head
,remove_tail
也都是由前置處理器處理。這技巧稱為 X macro
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →constant.c
中的measure
函式負責判斷ih
、it
、rh
、rt
是否有正常運作 (透過 size 有沒有隨著 insert 以及 remove 正常的變動)fixture.c
中的report
函式則負責測量是否為 constant time此處流程可以參照 preparaz/dudect,中的 Checking your code for constant time 節
Student's t-distribution 及程式實作原理
實作放在
ttest.c
相關函式,按照公式計算t
值\[ t = \frac{\bar{X_0} - \bar{X_1}}{\sqrt{\frac{s^2_1}{N_1} + \frac{s^2_2}{N_2}}} \]
其中
t
值越小代表兩組分佈越接近,注意這裡的 \(\bar{X_i}\) 指的是 sample mean首先在進入定義全域變數
t_constext *t
,結構體定義為在每一次測試時 (預設測試 10 次 (
TEST_TRIES=10
)),首先呼叫init_once()
,在init_once()
中會將t
的各個元素初始化。接著根據不同的
mode
(不同的 case 有不同的 mode number) ,進入doit
,在prepare_input
之後進入measure
函式,measure
函式根據q_size
判斷ih
,it
,rh
,rt
等操作有沒有順利進行,也計算函式執行時間,以insert_head
為例:函式若正常執行回傳
true
給ret
,且更新了before_ticks
以及after_ticks
交給differentiate
進行計算exec_time[i] = after_ticks[i] - before_ticks[i]
,其中i
為每次test
進行的 measurement 次數,預設為 150 次。接著將計算出的
exec_time
及classes
送入updata_statistics
,函式內透過t_push
進行 t-test ,t_push
需要的參數包含t_context
本體t
difference
也就是對應的exec_times[i]
classes[i]
在 dudect.h 原始碼中有提到可以參考《The Art of Computer Programming, Volume 2 : Seminumerical Algorithms》 (待研究),看起來是透過取新的 execution time 與當前平均值的差為變化量 (
delta
) 而新的平均值就會是舊平均值加上變化量的平均值 (delta/n
),可以避免每次都將所有數值相加取平均的過程,而m2
為變異數尚未平均的值,在後續t_compute
會使用到,每次的m2
更新為舊m2
加上delta
乘上這次的 execution time 與更新過後的平均值的差。在
ttest.c
中之後進入
report
函式透過t_compute
及取絕對值的fabs
計算max_t
,max_t
數值就會根據t_threshold_xx
的值來判斷程式是否 constant time,這裡程式的實作如同上述給定的公式\[ t = \frac{\bar{X_0} - \bar{X_1}}{\sqrt{\frac{s^2_1}{N_1} + \frac{s^2_2}{N_2}}} \]
再回傳
t
值之後會在透過fabs
取絕對值。修改以達成 constant time
當前
lab0-c
並未實作 percentile 功能,也就是沒有將測量誤差給 crop 掉,將 dudect.h 中的 percentile 引入後即可順利通過trace-17-complexity
dudect.h 中的結構體定義和
lab0-c
有一些不一樣,原專案中使用dudect_state_t
將ttest_ctx_t
(等效lab0-c
中的t_context_t
) 及執行時間、輸入資料、等必要資訊定義在一起,但在lab0-c
都是拆開的,在這邊引入percentile
至t_context_t
中詳細改動在 commit 中。
研讀 list_sort.c 並引入專案
將 list_sort.c 以及 list_sort.h 引入專案,並新增
makefile
的 dependency 以及把無關的 include header 移除list_sort.h
編譯後發現
likely
以及unlikely
沒有定義,參考 linux/compiler.h,其中的__builtin_expect()
是 gcc 的 built-in function,用來將 branch 的相關資訊提供給 compiler,讓其對程式碼改變分支的順序在
list_sort
的 prototype 中需要三個參數,priv
,head
,以及cmp
head
為我們要輸入的 queue 的head
cmp
為 compare function,作為函式指標傳入,根據註解cmp
return > 0 (a 排在 b 的後面)cmp
return <=0 (a 排在 b 的前面或保留原本排列)list_sort
是一個 stable sort,故不用區分小於 0 或等於 0不確定這邊的
priv
是要幹麻的,不過根據宣告priv
可以為NULL
參考
do_sort
新增do_lsort
代表 linux 核心原始程式碼的list_sort
並定義cmp
提供新命令
lsort
在
qtest.c
的console_init()
中新增命令結果呈現
git commit
的時候會被擋下來考慮到不要修正
list_sort.c
的原始碼,用cppcheck-suppress
跨過去效能比較
使用
perf
進行效能比較,參考 Linux 效能分析工具: Perf定義測資
<sort>
可以為原先qtest.c
中撰寫的do_sort
或是引入的do_lsort
,透過time
命令獲取sort
執行的時間,設定--repeat 5
重複五次,並透過perf
拿到instructions
以及cycles
的數據自行撰寫的
q_sort
list_sort
雖然每次執行這個測試案例時,Instructions 和 Cycles 的數量會些微變動,但整理來看
list_sort
指令較多,Cycles 較少在 qtest 提供新的命令 shuffle
選取一個隨機數,將該位置元素取出後放到尾端,舉例來說
在命令直譯器中新增命令 shuffle
在
qtest.c
中的console_init()
新增shuffle
指令查看其巨集定義
此巨集會將
cmd
命令的行為去透過巨集展開do_##cmd
去呼叫對應 handler ,如其他命令new
會去呼叫do_new
函式,故在此定義do_shuffle
函式來 handleshuffle
命令參考其他函式 handler 的寫法,有些函式內有定義
bool ok
,有些則無,觀察到ok
都用在佇列長度有更改或者需要判斷佇列是否為空的情形,所以reverse
,swap
中不需要ok
來處理佇列長度改變,且對應的do_reverse
以及do_swap
中都有相應的if(!head)
來處理空佇列的問題,綜上所述do_shuffle
不需要有bool ok
定義。q_shuffle
宣告加在queue.h
所以就放在do_shuffle
的上面運用 Valgrind 排除 qtest 實作的記憶體錯誤
首先嘗試用
valgrind
跑跑看trace-01-ops.cmd
測資依據網站及作業講解敘述,Valgrind 有 5 種 memory leak
這裡發生的 still reachable 在
do_new
及test_malloc
中,但是如果直接執行,qtest
且逐行輸入命令不會有這個問題。沒什麼頭緒,參考 yanjiew1,上述問題已在 #121, #122 被修正
TODO: 搞懂別人做了什麼
透過 Massif 視覺化 "simulation" 過程中的記憶體使用量
選用
trace-17-complexity.cmd
先透過 Valgrind 產生 massif 檔案
再使用
massif-visualizer
查看trace-eg.cmd
進行分析Reference
參考資料不是讓你發表致謝詞的地方
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →