contributed by < SPFishcool
>
注意書寫: doubly-linked list,連字號的位置在 doubly 和 linked 之間。
:notes: jserv
使用 Graphviz 製圖並嵌入到 HackMD 頁面。
:notes: jserv
收到!正在改進中!
element_t
為佇列儲存 value,內部有 list_head
架構的 list 能夠將所有 element_t
串接起來queue_chain_t
結構的物件,其功用類似上述佇列結構的 head,目的是每當 q_new
一個新的佇列時會將新的佇列利用 list_add_tail 加入到 queue_chain_t
鏈結串列的最後面。
queue_chain_t 架構圖,size 為佇列的數量
queue_contex_t
在紀錄每一條佇列的資訊,*q
為 pointer of queue head,size
是佇列裡面 element_t 的數量,chain
為與其他 queue_contex_t
串連的 list_head
。
queue_contex_t架構圖
queue_chain_t
其完整佇列架構就完成了!
queue 為上圖的紅色虛線部份
q_new()
會先配置 list_head
大小的記憶體位置,接下來判斷是否配置成功,最後 INIT_LIST_HEAD(new)
會將這個 node 接回自己(成為 Doubly-linked list 型態)。
q_free()
從 head->next
(第一個有entry的節點)開始釋放,直到回到 head 最後釋放 head 節點。
兩者可以使用 list.h 中的 list_add
與 list_add_tail
將 element_t
內的list加入鏈結串列中。
remove 只有將 element_t 的串列節點從佇列中移除並沒有釋放其空間,因此只需要將其 next 與 prev 連接起來即可移除。
q_size
則從 head->next
開始計算這條佇列的 element_t
數量,並且回傳 i
(count 後的結果)
因為佇列是 doubly-linked list 結構,因此可以使用左右指標(最左和最右可以快速找到),其原理是每一迴圈 left_node
往右走一步,right_node
往左走一步,直到兩指標相遇(奇數 element)或相鄰(偶數 element)時 right_node 就是我們要找的節點。
優點:跟快慢指標相比,迴圈節省了一半的次數(僅限 Circular Doubly-linked list)。
q_delete_dup
這個 question 我紀錄此次和前次的比較紀錄,而受到「判決」的節點為
indirect->prev
(目前指到的節點的前一個節點)
cmp
:indirect
與 indirect->prev
的比較結果prev_cmp
:indirect->prev
與 indirect->prev->prev
的比較結果cmp
還是 prev_cmp
只要為 0 都代表著中間這個節點的 value 與前後節點重複,必刪除!q_swap
只要將佇列內的 element 兩兩交換即可,因此只需要使用 list.h 中的 list_move
把自己搬到 next
後面即可
這裡則是使用 pointer to pointer 紀錄第一個 element 節點內的 next 位址(&head->next->next
),在每次迴圈中將 next
指到的節點利用 list_move
移到 head
前面直到 next
指向的位置為 head
為止。
q_reverseK
其原理與 q_reverse
相似,但因為如果後面個數不足 k 個則不做 reverse,因此我的作法是先讓一個指標邊走邊數,數到第 k 個後,再將後面的節點一一搬到前面來,因此如果走完一圈沒有數到 k 個,剩下的節點就不會動到。
參考你所不知道的 C 語言: linked list 和非連續記憶體merge_sort寫法。
原先想法是要維持 doubly-linked list 的結構去設計,但當我要 malloc
新的 head 來接分割後的子串列時遇到ERROR:Calls to malloc disallowed
,因此決定將 circular 斷開將最後一個 node 的prev
指向NULL
才進行mergesort
並且在排序後將排序過後的串列重新接上 head。
q_descend
若是 prev
方向有比自己小的節點則這些小的節點都要 delete 掉,直到遇到下一個比自己大的節點,則移動到下一個節點(prev
)
在 qtest.c 裡執行q_merge
其程式碼為
這個 chain 其實就是 queue_chain_t
結構,所以得知輸入的資料為 queue_chain_t
內的 head
元素因此我們可以很簡單的拜訪所有佇列再使用list.h中的 list_splice_init
從第二個佇列到最後一個一一串在 目前第一個 id=0
queue_contex_t->q
的串列內。
qtest
執行結束未妥善釋放其程式所配置的記憶體所導致而上述 problem 的錯誤為:
在這情況下我決定先嘗試解決比較輕的問題,解決過程如下:
發現 do_quit
第一行程式碼即 return true
表示下方程式完全沒有走過,註解掉後重新 make valgrind
所有問題都解決了。
在 linux 核心原始程式碼內的 lib/list_sort.c 所作的 sort 為 Merge Sort ,但這裡面的 Merge Sort 跟我們以前認識的 Merge Sort 不同。
在 list_sort
內有 3 個 Function
merge
merge_final
list_sort
而走訪 linked list 時會使用 count 會計算目前走訪到的節點數量,一旦走訪數量走到 數量時不執行 merge
,反之就會執行 merge。其流程如下
list
從 head->next
開始,而 pending
起始為 NULL
,count
起始為 0
,linked list 最後指向 NULL
。**tail
指向 pending
的位址。count
是否為 ,如果是,跳到 "7."。merge
,將 a = *tail
、b = a->prev
,執行 merge(priv, cmp, b, a);
merge
會有一個 **tail
紀錄前一次比較最小的節點的 next
位址,每一次比較會把 *tail
指向較小的節點,其實就是一般的merge,這裡就把 next
拿來做 merge 的主要 link。a
,將目前的 a->prev
重指成 b->prev
,藉由 *tail
把 pending
指向 a
(merge 起始節點)。list->prev
指向 pending
,list
往前一步, panding = list->prev
, 把 pending->next
指向 NULL
。list
還沒走完,回到 "2."merge_final
把 merge 完的 list 將其 prev
連接起來。看完 lib/list_sort.c 發現他並沒有特別做 divide 而是走訪過程一一將 next
斷開並且把 next
當成執行 merge 動作時走訪的 link,比較「你所不知道的 C 語言: linked list 和非連續記憶體」所提及的遞迴與非遞迴,此做法省去 divide 這一部份,也沒有使用 stack 有 Max Depth 的限制。
ADD_COMMAND
新增剛才加入的 list_sort
, 再依照檔案裡的 do_sort
新增 do_list_sort
list_sort
與自己做的 q_sort
效能比較參考 linhoward0522 開發紀錄 (lab0) 使用 perf 分析其效能,在 traces/trace-15-perf.cmd 有三筆資料來測試排序,因此我們可以依照這個檔案自製用來測試的 cmd 檔。
q
: q_sort
list
: list_sort
接下來是試做 shell scripts
q_sort
#node | cache-misses | branches | instructions | context-switch |
---|---|---|---|---|
1000 | 3469 | 77,9884 | 516,0401 | 0 |
10000 | 3,3403 | 621,9374 | 4305,8950 | 0 |
100000 | 543,0532 | 6326,2119 | 4,3295,3471 | 0 |
1000000 | 7491,5547 | 4,6974,1917 | 31,6153,3055 | 10 |
list_sort
#node | cache-misses | branches | instructions | context-switch |
---|---|---|---|---|
1000 | 2489 | 77,7798 | 519,9240 | 0 |
10000 | 4,1257 | 614,2028 | 4329,3845 | 0 |
100000 | 439,1922 | 6258,0696 | 4,3642,0292 | 3 |
1000000 | 5824,6891 | 4,6028,3507 | 31,6579,3448 | 9 |
可以看到節點數增多,效能差異越大,雖然 RAND 會導致偶爾會讓 list_sort
效能看起來跟 q_sort
差不多甚至更差,但時間上還是 list_sort
略勝一籌。
shuffle
命令作業要求為使用「Fisher–Yates shuffle」完成 shuffle
,其原理就是從最後一個節點開始,隨機選取前面的節點與其交換,交換完後再往前一格完成前面敘述的事情直到第二張牌為止。
跟設定 list_sort
一樣,新增 do_shuffle
與 ADD_COMMAND
接下來統計剛設計出來的 shuffle
亂度
參考 L01: lab0 亂數+論文閱讀 中的測試程式並載入 Makefile 執行指令
執行結果:
依照 4 個數字的亂數來看,分佈差距大約在 ,學生突然想到如果數字在多一點的話分佈會不會變化比較大,因此我將數字加到 6 個,而 6 個數字的排列組合比 4 個數字多出 30 倍,因此也將次數增加到 30000000 次。
執行結果:
在這張圖最少次為 41081,最多次為 42281,幅度有稍微增加,但沒有特別明顯。
在認識 entropy 之前,對 entropy 的唯一認知就是機器學習中計算 loss 值的方式 cross entropy
,發現其中的含意與 entropy 也有一些關係,但知道了「entropy 與亂數產生的資訊量成反比」還是覺得對 entropy 有點抽象,因此我打算從 qtest.c 裡的指令 option entropy 1
與 ih RAND 10
找出關於 entropy 的蛛絲馬跡,最後發現 shannon_entropy.c 裡的 shannon_entropy
實作了以下 entropy 公式。
這裡除了使用 bucket 計算每一個字元的熵值,而且使用 LOG_ARG_SHIFT
預計算的常數來避免使用浮點數增加計算時間。