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
2022q1 Homework1 (lab0)
contributed by < cwl0429 >
tags:
linux2022
實驗環境
作業要求
q_new
: 建立新的「空」佇列;q_free
: 釋放佇列所佔用的記憶體;q_insert_head
: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則);q_insert_tail
: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則);q_remove_head
: 在佇列開頭 (head) 移去 (remove) 給定的節點;q_release_element
: 釋放特定節點的記憶體空間;q_size
: 計算目前佇列的節點總量;q_delete_mid
: 移走佇列中間的節點,詳見 LeetCode 2095. Delete the Middle Node of a Linked Listq_delete_dup
: 在已經排序的狀況,移走佇列中具備重複內容的節點,詳見 LeetCode 82. Remove Duplicates from Sorted List IIq_swap
: 交換佇列中鄰近的節點,詳見 LeetCode 24. Swap Nodes in Pairsq_reverse
: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點;q_sort
: 以遞增順序來排序鏈結串列的節點,可參閱 Linked List Sort 得知實作手法;實作細節
q_new
list.h
中的INIT_LIST_HEAD
將 next 和 prev 指向 head 本身並且回傳 head,否則回傳 NULL 表示配置失敗q_free
list_for_each_entry_safe
找到 queue 中的每個 element 並釋放list_for_each_entry
,是因為在迭代的過程中會將 element 刪除,list_for_each_entry
沒有對 node 做保護,若隨意刪除節點,將會無法找到下一個 node用通順的漢語書寫。
- 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
element_t *e
,若是無法配置則回傳 falselist_add
將 e->list 加入 queue 的開頭並回傳trueq_insert_head
判斷式,當 queue 尚未被建立時,不能插入節點,應回傳 falseq_insert_tail
q_insert_head
相似,唯一差別在於 e->list 加入的位置list_add_tail
將 e->list 加入 queue 的 tailq_insert_head
一樣問題q_remove_head
list_first_entry
取得 queue 的第一個 elementlist_del
將該節點移出list_del
只有將 link 斷開,以下為list.h
註解:qtest.c
中do_remove
會主動將 \0 填入字串的末端q_remove_tail
q_remove_head
雷同,只差在移除 head->next 還是 head->prevq_size
lab0-c
作業說明,目前還沒想到更好的寫法d_delete_mid
核心主體精神為Floyd's tortoise and hare
演算法在本課程中,「核心」一定指 OS kernel,你該避免濫用該詞彙。附上必要的參考資訊。
- 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
list_move
將當前 node 插入 node->next 的 nextq_reverse
list_for_each_safe
q_sort
q_reverse
沒有寫好,導致有 link 連接錯誤,想說試試list_move
的方式實作,便成功通過測資q_size
改寫成反方向 traverse 後,才發現 merge 結束時 queue size 已經不對,代表q_sort
只有正確地連接順向的 nodel
的l->prev
沒接到q_reverse
實作方式會通過測資的原因在於list_move
串接了遺漏的 link,使 queue 在經過 reverse 後能正確地 free遇到的錯誤訊息
根據 Scottk 網友在 stackoverflow 的說明,似乎是 strcmp 會建立一個 assembler file - strcmp-avx2.S,但由於傳進去的 pointer 為 NULL pointer,導致它無法創建該檔案,但可能要找到更多證據來支持這項論述
請愛用 GDB 排除問題。
- 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_delete_dup
strcmp
不能接受 NULL 值,所以要使用一段判斷式跳脫迴圈,避免 safe->value = NULL 傳入strcmp
研讀及比較 lib/list_sort.c
研讀 list_sort.c
__attribute__
用途根據 gcc 的說明,
__attribute__
能指定變數、函式參數及結構等等的屬性其中在 list_sort.c 用到 function attribute,其能幫助編譯器最佳化及更正確地檢查 code 是否出錯
譬如 list_sort.c 用到不少 nonnull attribute,能指定函式的哪些參數不能為 NULL pointers,以下方例子來說,
nonnull(2,3,4)
函式第二到四的參數不能為 NULL pointers,也就是 cmp, a, b 這三個參數Merge 方式
list_sort.c 使用了兩種 merge 函式
merge
使用指標的指標技巧
宣告兩個變數
* head
及** tail
實際 merge 方式
利用 cmp 比較 a b 大小,若是小於或相等則選擇 a 為下一個 node,用來確保 sort stability
將 a 放入 tail 指向的地址,也就是 head 的地址
將 tail 指向的地址改為 &a->next
將 a 指到 a->next
接著重複此過程,不斷地將 a or b 串接起來
直到 a or b 為 NULL,便將非 NULL 的 list 串到 *tail 指到的地址
在操作之後
cmp(priv, a, b) > 0
,則將上列操作 a, b 對調即可merge_final
和 merge 的差異為,merge_final 會將所有的 list->prev 串接至正確的 node
將 tail->next 串接至 b 後,再把後續的 b->prev 串接完成
需要注意的地方為以下部分:
!++count
: 由於 count 為 u_int8,代表在 count 遞增至 255 前,傳入unlikely
的數值皆為 0!++count
為 1unlikely
是一個巨集,定義在compiler.h
,定義內容如下:# define unlikely(x) __builtin_expect(!!(x), 0)
!!(x)
用途是使任何變數在兩次 not 後,剩下 0 或 1 兩種可能unlikely
的回傳值為實際上!!(!++count)
的結果unlikely
會影響編譯器的 branch prediction,表示在該 if 條件下的 code,較不常被執行,進而將此段 code 擺到較為後方的位置至於為何需要比較相同的數值呢 ? 根據
merge_final
內的註解 :cmp(priv, b, b)
是為了定期喚醒cond_resched
,使得 cpu 的資源能被短暫釋放但為何呼叫
cmp
能連帶觸發cond_resched
,目前還沒搞懂list_sort
首先來看
list_sort
的註解得知此 mergesort 的實作方式,是希望被 merge 的 sublists size 比例至少為 2 : 1 (較大的為較小的兩倍以上)
常見的 merge sort 會在遇到兩個大小為 \(2^k\) 的 list 時將其 merge,但 linux 的 merge sort 只有在兩個大小為 \(2^k\) 的 list 位於 pending 且再遇到一個 \(2^k\) 的 list 時,才會將 pending 中的兩個 \(2^k\) 的 list merge。若是此 \(3*2^k\) 的 list 能放入 cache,則可避免 cache thrashing 發生
裡面提到 count 是控制放入 pending 的 list 數量,會根據當前 count 而有不同效果,共有六種狀態 :
接著來看實際程式碼
for (bits = count; bits & 1; bits >>= 1)
likely(bits)
和merge_final
中提到的unlikely
相似,都能藉由此巨集幫助編譯器最佳化Risheng1128
所作的研讀 Linux 核心原始程式碼 list_sort建立完 pending 後,此時 pending 內是由小到大排列完成的 sublists,剩下就是把 pending 內的所有 sublists 進行合併
最後將 list 內的 prev links 串起,便完成此 sort
比較 mergesort 效能
將
list_sort
引入將
list_sort.c
及list_sort.h
放入專案中,並將遺失定義的巨集補進list_sort.h
內新增 cmd 的 option : list_sort
用來決定要使用 linux 的 sort 或 自己實作的 sort
實際比較
先設計一個自動測試檔
trace-18-compare.cmd
使用大小為 \(262144 (2^{18})\) 的資料進行排序
結果如下:
可以發現 my sort 的 delta time 約為 linux sort 的 2.1 倍
接著嘗試利用 perf 進行 mergesort 效能分析
我將兩種 sort 各跑 5 次,perf 會將結果作平均
linux sort
my sort
list_sort()
q_sort()
我發現 cache-misses, cache-references 及 instruction,兩者的表現接近,但在比較 cycles 時,my sort 比起多了 27 億個 cycles,差異不小
新增 shuffle 命令
看懂 Fisher-Yates shuffle,想法如下:
將 shuffle 加入
console_int()
內便能利用 shuffle 命令將 queue 隨機洗混,操作如下:
新增 web 命令