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
2024q1 Homework1 (lab0)
contributed by <
ssheep773
>Reviewed by
SuNsHiNe-75
- 請把完整程式碼移除,如要討論才將要討論之部分「重點列出」。 - 注意標點符號的使用,有些地方都沒有「句號」。 - 進度太慢。 - 可善用 Valgrind 工具來追蹤記憶體使用狀況。Reviewed by
stevendd543
Reviewed by
st10740
q_swap
中交換兩兩節點位置的方法可以使用list.h
提供的list_move
方法取代list_del_init
和list_add
,可以達到一樣的效果且更簡潔。開發環境
指定的佇列操作
q_new
使用
list.h
中的INIT_LIST_HEAD
來初始化struct list_head
q_free
使用
list.h
中的list_for_each_entry_safe(entry, safe, head, member)
走訪每個節點。在走訪的過程中,可以對
entry
作任意操作,且不影響後續節點的走訪,利用這個特性逐個節點刪除。目前的例子中則是透過
element_t
中的struct list_head list
q_insert_head
list.h
中的list_add()
函式,將新增的節點添加在佇列的開頭strdup()
可以動態配置字串的記憶體空間,沒有使用strncpy()
的原因是需要另外計算字串長度q_insert_tail
作法與
q_insert_head()
差異在於節點插入的位置不同,使用list_add_tail()
函式將節點新增在尾端。commit
q_remove_head
使用到
list.h
中的函式list_first_entry()
回傳 head 的第一個節點list_del()
雖然名稱有 del 但並沒有將節點刪除,而是將節點 node 從 linked list 上 remove,成為單獨的節點q_remove_tail
作法與
q_remove_head
差異在於移除節點的位置不同,使用list_last_entry()
函式獲取鏈結串列的尾端節點q_delete_mid
使用 你所不知道的 C 語言: linked list 和非連續記憶體 中提到的快慢指標,找到中間的節點並刪除
q_delete_dup
參考 Risheng 進行實作
函式假設:
*head
是排序好的鏈結串列變數說明:
cur
: 指向當前節點的指標next
: 指向cur
下一個節點的指標flag
: 表示是否發生資料相同的情況因為
head
是排序好的串列使用,所以加入!strcmp(cur_entry->value, nxt_entry->value)
作為迴圈中止的條件減少非必要的項目縮排 (即
*
),使用清晰、明確,且流暢的漢語書寫。q_swap
避免非必要的項目縮排 (即
*
,-
或數字),以清晰、明確,且流暢的漢語書寫。授課教師在大學教書十餘年,見到不少學生在課堂表現不俗,但在面試場合卻無法清晰地闡述自己的成果和獨到的成就,從而錯過躋身一流資訊科技企業的機會。為此,要求學員書寫開發紀錄,並隨時做好「當主管問起,就能清晰解釋自己的投入狀況」,是強調讓學員「翻身」的本課程所在意的事。
濫用條列 (或說 bullet) 來展現,乍看很清爽,但在面試的場合中,就很難用流暢的話語說明。反之,若平日已有這方面的練習,則面試過程中,才可充分展現自己專業之處。
建立兩個指標
first
和second
使用
list_del_init
將first
指向的節點從linked list
取出使用
list_add(first, second)
將first
指向節點加到second
節點指向的下一個節點位置,達到兩節點交換位置first
指向 node C 結束這次的迴圈經由
st10740
的建議我再次查看list_del_init
、list_add
、list_move
這三個函式,我發現list_move
的操作跟list_del_init
加list_add
的操作相比,只是少了過程中移除
node
後的節點初始化,而在swap
的過程中我們其實不需要將移除的節點初始化,因為我們馬上會在後續的list_add
為其分派指標位置q_reverse
使用
list_for_each_safe
走訪原始的鏈結串列,並使用list_move
將節點移至串列的開頭,結束後即可得到反轉的鏈結串列q_reverseK
cut
: 指向未完成反轉的串列count
: 紀錄訪問節點個數每當訪問 K 個節點, 就將那 K 個節點透過
list_cut_position(&tmp, cut, node)
切出來放到tmp
,並透過q_reverse()
反轉,使用list_splice(&tmp, cut)
把tmp
接回cut
,最後更新cut
指向未完成反轉的串列q_merge_two
參考 王彥傑 同學進行實作,發現在決定排序的程式碼可進行優化
透過真值表說明
descend
與cmp <= 0
的關係是互斥或 ( XOR )descend
: 決定串列是否遞減cmp <= 0
: 表示 L1 數值較小list_move_tail()
: 將節點新增到串列中q_sort
參考 yanjiew1 同學進行實作,同時改成使用快慢指標來尋找中間節點
你如何確認目前的測試方式/資料已涵蓋排序演算法的最差狀況?
若要實作分治法(Divide and conquer)則需要尋找串列的中間節點,我首先想到可以使用
q_delete_mid
中找中點的方法,然而在q_sort
上卻會出錯實作下列的修改後雖然可以執行,但是
mid
的數值都是等於串列的第一個節點,由此推斷在原先的迴圈結束後slow
會等於head
修改初始值
*fast = head->next->next
,則可以成功執行,但其中的緣由還在釐清當中q_ascend
/q_descend
q_ascend
和q_descend
使用相同的方法,差異只在其中strcmp()
比較節點資訊的是大於還是小於這裡以
q_descend
作範例說明避免非必要的項目縮排 (即
*
,-
或數字),以清晰、明確,且流暢的漢語書寫。授課教師在大學教書十餘年,見到不少學生在課堂表現不俗,但在面試場合卻無法清晰地闡述自己的成果和獨到的成就,從而錯過躋身一流資訊科技企業的機會。為此,要求學員書寫開發紀錄,並隨時做好「當主管問起,就能清晰解釋自己的投入狀況」,是強調讓學員「翻身」的本課程所在意的事。
濫用條列 (或說 bullet) 來展現,乍看很清爽,但在面試的場合中,就很難用流暢的話語說明。反之,若平日已有這方面的練習,則面試過程中,才可充分展現自己專業之處。
我們會走訪串列的每個節點,並且同時檢查它右邊節點是否大於目前的節點,若成立則表示目前的節點需要刪除,才能滿足串列是遞減的,並使用
flag
紀錄目前節點需要刪除,在後續的程式碼中刪除此節點。開發紀錄:
原先沒有使用
tmp
先儲存cur->next
,造成讀取到遭刪除的cur
在除錯過程中發現,因為編譯器會執行程式的最佳化,使程式的執行不是如程式碼一樣線性的執行,所以我在除錯過程中,有先關閉編譯器最佳化
經由
stevendd543
的建議,我將複雜度降到 \(O(n)\) ,我首先使用cur
與next
指向目前節點與目前節點的前一個節點,因為我們要倒著走訪整個鏈結串列,在走訪的過程中我們會比較目前的節點與前一個節點的值,若目前的節點較大,等於是說在串列中有一個節點比它後面的節點小,這樣就違反遞增的條件需要刪除,而若是符合遞增條件,則將會往下一個節點走訪。原本的作法的複雜度會是 \(O(n^2)\) ,因為每個節點的走訪都是獨立的,每次都需要重新走訪所有目前節點的所有右邊的節點,等於是我們重複走訪最後一個節點 n-1 次
而目前方法則是用
cur
紀錄目前值最大節點,因為若是節點符合條件,則表示他是目前最大的節點,透過走訪刪除不符合的節點或是更新目前的最大節點,來達到嚴格遞減的串列上面程式在 make test 的過程中,會出現存取 NULL 指標的錯誤情況出現,我分析是因為當
next
被刪除後,又在後續執行next = next->prev
時存取next
,所以我將程式修改如下,改成透過不會被刪除的cur
執行走訪q_merge
首先了解要合併的資料格式,是由許多
queue_contex_t
串接而成,我們要合併的佇列是在queue_contex_t->q
我使用的方法事先將所有的
q
都取出來存在tmp
最後再對tmp
作排序,以及計算他的長度在
make test
測試時,trace-17-complexity.cmd 無法每次檢測都通過,目前推測是無法在常數時間內完成Valgrind + 自動測試程式
使用 massif 得到分析數據,會得到行程在記憶體使用狀況的 snapshot 。
使用 massif-visualizer 圖形化數據
使用 Valgrind 驗證程式後,並沒有出現記憶體使用錯誤的狀況,但仍然會有錯誤訊息
ERROR: Probably not constant time or wrong implementation
運行 trace-017-complexity

在指令運行階段,此時佔用堆積最主要的函式是 test_malloc ,也就是在運行
q_insert_head()
和q_insert_tail()
時會用來分配記憶體的函式實作
shuffle
命令指令command 是「命令」,而非「指令」(instruction)
實作 Fisher-Yates shuffle Algorithm
透過閱讀 Fisher–Yates shuffle 來實作亂數演算法
其中因為無法更改
queue.h
,我另外新建shuffle.c
,當我要 commitshuffle.c
時,會出現錯誤我在參考 SHChang-Anderson 同學的 commit 後,加入註解
// cppcheck-suppress nullPointer
就可以忽略警告,詳細可以看cppcheck.net(網址待更新)統計方法驗證
利用 lab0-d 提供的程式碼測試亂度
從圖表來看 shuffle 的頻率大致符合 Uniform distribution
論文閱讀 〈Dude, is my code constant time?〉
這邊論文的目的是評估一段程式碼是否是常數時間執行。
而常數時間執行的程式,可以確保程式或系統的安全性,避免有心人的攻擊,像是常見的旁路攻擊 (Side-channel attack) 會用於破譯密碼,通過分析執行加密演算法所花費的時間來嘗試破解密碼系統。
論文提出的方法是從使用者的角度或者是說從攻擊者個角度出發,因為當我們無法通過不斷執行程式,達到嘗試破譯密碼時,其實就算是達到常數時間執行,讓使用者無法推測出程式的執行邏輯。
這個方法與現有常數時間檢測方法的差異在於不需要模擬硬體設備。
TIMING LEAKAGE DETECTION
實驗的理論是根據執行時間的 leakage detection test ,透過觀察兩個不同測資的執行時間分布是否有顯著的差異。
step 1. Measure execution time
使用 fix-vs-random tests 的方法測試,這種方法是使用兩組測資,一組是每次都固定的測資,另一組是每次都會隨機產生的測資。
step 2. Apply post-processing
典型的時間分佈會向較長的執行時間方向傾斜,這是因為當主程式執行時,可能會被作業系統中斷,造成測量誤差
(measurement artifacts),我覺得這步驟主要是彌補沒有對硬體進行模擬。
step 3. Apply statistical test
使用 Welch's t-test 的統計分析,這個方法的特點是可以對樣本大小與資料具有不同變異數的資料進行分析。
兩次測試即使一開始設定相同的樣本的大小,也會因為後處理導致最後的樣本數出現差異,這時使用 Welch's t-test 就十分適合。
我比較論文提供的程式 dudect 與 lab0 程式碼沒有 step 2 的後處理處理測量誤差
commit 5bf3fe0
研讀並嘗試引入 Linux 核心原始程式碼的 lib/list_sort.c
老師的講解筆記
我所使用的 merge sort 方法是 top-down ,雖然使用更改串列連結的方式,可以改善原有方法對於 cache 的不友善,然而使用遞迴的方式可能會造成 stack overflow
Linux kernel 中 list_sort 所使用的方法是 bottom-up ,他是 in-place 直接在原始的數據上實作,使用到 cache 的 Temporal Locality ,畢竟鏈結串列應該是較難發生 Spatial Locality
list sort 主要是要處理原本 bottom-up 在合併時,需要較多比較次數,而因為比較是需要透過呼叫 cmp 函式,較多的比較次數也就代表需要更多的函式呼叫成本。
list sort 透過改變合併規則,確保每次合併的兩個子串列的大小比例不超過 2:1,以提高排序效率。
嘗試引入
lib/list_sort.c
首先,將 lib/list_sort.c 和 linux/list_sort.h 引入本專案中
首先刪除程式碼中不必要的 include
並發現程式碼中
u8
並未定義。而原本是在 linux/types.h 中被定義為uint8_t
,而uint8_t
是由 stdint.h 所提供。因此在 list_sort.h 中新增#include "stdint.h"
,並將u8
更改為uint8_t
,以確保相關型別的定義正確。接著編譯時出現
implicit declaration of function ‘unlikely’
發現程式碼中的 likely 與 unlikely 並未被定義,這裡參考 [Linux Kernel慢慢學]likely and unlikely macro 中的說明:在 Linux kernel 2.6 之後提供了 likely, unlikely 這兩個巨集,被定義在 /include/linux/compiler.h 中,用於告訴編譯器程式碼中哪個分支轉跳的機率高,幫助編譯器作優化。
將 likely, unlikely 這兩個巨集的定義加入 list_sort.h
最後我們還會遇到
error: data definition has no type or storage class
是在 lisy_sort.c 的最後一行,目的是要使list_sort
可以在 kernel 內部自由的呼叫使用,但我們並不需要這個功能所以刪除。其中 list_sort 的呼叫須傳入比較函式 cmp ,我參考 chiangkd 同學的實作,在 qtest.c 中新增一個比較函式 cmp ,並以此為參數傳入 list_sort。
效能比較
參考 chiangk 的比較方法,並使用 pref 分析執行狀態
trace-sort.cmd
執行時間比較
我實作的 q_sort 執行時間表:
linux 的 list_sort 執行時間表:
根據上述實驗結果,相較於我自己實作的排序演算法,list_sort 在執行時間呈現出更佳的效能表現。
ttt
1.將 Linux 核心的 linux/list.h 標頭檔與 jserv/ttt 中 list.h 相關的程式碼抽出,成為
hlist.h
我在查看 linux/list.h 時發現,linux kernel 有使用
READ_ONCE
WRITE_ONCE
來實作 lockless contexts 避免 load-tearing (後續要查看READ_ONCE
WRITE_ONCE
功能)2.我將原有的
ttt/main.c
改寫成 ttt_main.[ch] 方便之後在 qtest.c 中呼叫在引入的過程中仿照 dudect 的方式將 agents 加入 Makefile ,而在 commit 時會出現出現許多
[constVariable]
與[variableScope]
的修正提示並使用 Monte Carlo tree search (MCTS) 演算法,確保使用定點數來取代原本 jserv/ttt 的浮點數運算。