contributed by < ire33164
>
Linux Kernel
這是 linux 核心設計課程 的第一次作業,主要用來加深對 singly linked list 的概念與實做
為了符合 C Programming Lab 中所提到q_size
和 q_insert_tail
的時間複雜度須為
Two of the functions:
q_insert_tail
andq_size
will require some effort on your part to meet the required performance standards. Naive implementations would require O(n) steps for a queue with n elements. We require that your implementations operate in time O(1)
因此在 struct queue_t 新增了兩個 field list_ele_t *tail
, int size
tail
: 型別為 list_ele_t *
, 用於紀錄 queue
最尾端的元素size
: 型別為 int
, 用於紀錄 queue
上目前串接的元素數量創建新的 queue
並初始化 :
malloc
是否成功size
設為 0 (空佇列head
, tail
皆指向 NULL
釋放 queue
中的 space
q
是否為 NULL
list_ele_t
和 value
)插入 element 到 queue
的 head
q
是否為 NULL
list_ele_t
的空間給 new element 並確認是否成功strlen(s) + 1
給 new element 中的 value
並確認是否成功next
指向 old headhead
queue
為空, 則將 tail
和 head
都指向 new elementstrncpy
將 s
複製到 newh->value
中size
+ 1關於 (3) 為什麼要取 strlen(s) + 1 ?
在 C 語言中,字串被以連續記憶體的方式儲存下來
並在字串的最後存下中止符號 '\0'
代表字串的的結尾
因此假設 string 長度為 k
則需要 k + 1
個字元空間才能儲存
再看看 strlen
的描述
The C library function size_t strlen(const char *str) computes the length of the string str up to, but not including the terminating null character.
From tutorialspoint
因此需要 allocate strlen(s) + 1
的空間
插入 element 到 queue
的 tail
q
是否為 NULL
list_ele_t
的空間給 new element 並確認是否成功strlen(s) + 1
給 new element 中的 value
並確認是否成功next
指向 NULL
strncpy
將 s
複製到 newh->value
中next
指向 new element 使其成為 tail
queue
為空, 則將 tail
和 head
都指向 new elementsize
+ 1移除 queue 的 head
, 若 sp
不為 NULL
, 則複製長度最大為 bufsize
的字串到 sp
中
q
是否為 NULL
或 emptysp
不為 NULL
, 則將 q->head->value
複製到 sp
中, 並將 sp
的最後一個 char
設為 '\0'
head
指向 head->next
改變 queue
的 head
size
- 1head
後 queue
為空, 則把 tail
也指向 NULL
list_ele_t
和 value
)strncpy
的描述
The C library function char *strncpy(char *dest, const char *src, size_t n) copies up to n characters from the string pointed to, by src to dest. In a case where the length of src is less than that of n, the remainder of dest will be padded with null bytes.
From tutorialspoint
當中提到若 n
大於 src
的字串長度時
其在 dest
上的差額會自動補上 null bytes
因此使用 strncpy 複製字串時可以直接給定最大長度
又因 strncpy
並不像 strcpy
會在複製後自動補上 '\0'
所以就必須預留最後一個 byte 給 '\0'
因此複製的最大長度為 bufsize - 1
回傳 queue
的 size
q
為 NULL
則回傳 0
size
反轉 queue
的排序
q
是否為 NULL
或 emptynext
指向前一個 elementqueue
中的 head
和 tail
對 queue
做依據 natural sort 排序,參考 測驗 1 使用 divide and conquer
的方式做排序
除了測驗一的演算法外,值得注意的是做完排序後須重新指派 head
和 tail
。
執行 $ make test
進行測試 :
可以看到剩下 trace-15-perf
和 trace-16-perf
未通過。
Time limit exceed
其中 trace-16-perf
顯示 Time limit exceed,經思考過後發現 測驗一 的演算法相當於將一個 left
插入已排序好的 right
中,概念類似 insertion sort,時間複雜度為 ,若將 queue
從中間切一半分成 left
和 right
再進行運算,概念類似 merge sort,時間複雜度則可從原本的 降為
利用
left
走一步right
走兩步的技巧,當right
或right->next
為NULL
時,left
就剛好在queue
的中間位置
改良後 :
執行 $ make test
:
通過測驗!!
不過我很好奇為什麼改完 trace-16-perf
後 trace-15-perf
也跟著修好了,於是我用原本的 code 利用 Valgrind
再測試一次 trace-15-perf
來分析記憶體的使用情形。
執行 $ scripts/driver.py -p /tmp/qtest.pSix6W --valgrind -t 15
:
Stack overflow
使用 Valgrind
測試後, 發現裡面存在著 stack overflow 的問題,所以猜測是原本的演算法遞迴呼叫太多次導致 stack
無法容納如此多的 return 的 address,在改良成 merge sort 後遞迴次數變少,不只時間複雜度下降, stack overflow 的問題也跟著解決
Natural sort - 3/1 更新
撰寫的過程中忘記要使用 natural sort 來代替原本的
strcmp
,因此補上進度
由於要使用 strnatcmp
因此先至 natsort 下載 strnatcmp.[ch]
加入專案中,接著將 strnatcmp.c
匯編至 Makefile
中進行編譯並且連結 :
將 queue.c
中 strcmp
換成 strnatcmp
並匯入 strnatcmp.h
,發現執行 make test
時 trace-perf-15 失敗,於是參考 merge sort iteration 對原本的程式碼稍做修改:
即可跑過測資
sort
是否成功出現了 ERROR: Not sorted in ascending order
於是回頭 trace qtest
中的 function do_sort
發現
2021/1/31 更新
因為之前寫的 code 太醜,於是把整個 do_sort 重寫:
Divid_and_conquer
split
將 queue 從中間切開,這邊採用 Fast & Slow Pointer 來找出中間的值:merge
tmp
是一個指向 head
的 pointertmp
指向的位置也就是 *tmp
的值目前跑測資依舊是 trace-15-perf fail,用 valgrind 分析記憶體使用:
由於 Time limit exceeded 是發生在採用 natural sort 後,猜測應該是 strnatcmp
帶來對效能的衝擊,使用 perf
測試 strnatcmp
與 strcmp
的差別:
strnatcmp
直接佔了 43.13% 的 overheadstrcmp
後因為少了 strnatcmp
的 overhead,trace-15 也可以輕鬆通過除了 strnatcmp
外,split
本身也佔了整體 20% ~ 29% 的 overhead,因此可能要從優化 split 下手。
利用 perf
找出 split
bottleneck:
TODO: 尋找優化方法
得到 unknown option: --show-leak-kinds=all
的結果,