contributed by < liangchingyun
>
1
解釋上方程式碼運作原理
巨集定義
my_assert()
: 如果測試條件 test
為 false
,則返回 message
。my_run_test()
: 若測試失敗,則直接回傳錯誤訊息。重設鏈結串列
items[i]
的 value
設為 i
,並將 next
設為 NULL
。l.head = NULL
; 清空鏈結串列。測試函式
test_list()
包含三種測試情境
在開頭插入
l.head
表示插入在最前面。cur->value
是否符合預期的逆序 (從 N-1
到 0
)。在結尾插入
NULL
表示插入在尾端cur->value
是否符合預期的正序 (從 0
到 N-1
)。重設串列並插入
這部分與 2. 類似,但主要是確認插入結果仍然正確。
測試執行
test_suite()
執行 test_list()
,並計算測試數量。
主函式
test_suite()
若發生錯誤,則 result
為錯誤訊息。ALL TESTS PASSED
,否則輸出 ERROR
訊息。list_insert_before
函式:
以 before = B
為例:
p
初始指向鏈結串列的頭指標。
for
迴圈會遍歷鏈結串列,直到找到 before
節點。
*p = item;
將前一個節點的 next
指向 item
,完成插入。
item->next = before;
讓 item
指向 before
,確保鏈結不會斷開。
在現有的程式碼基礎上,撰寫合併排序操作
2
解釋上方程式碼運作的原理,並嘗試補完程式碼,使其可獨立執行,應提供相關的測試程式碼
這段程式碼的功能是從二元搜尋樹 (BST) 中移除特定的節點,這個 BST 用於管理記憶體分配中的「空閒區塊 (free blocks)」。
結構體
block_t
代表一個記憶體區塊,具有:
* size
:區塊大小
* l
:指向左子節點(較小的區塊)
* r
:指向右子節點(較大的區塊)
remove_free_tree
用來從 BST 中移除 target
節點,遵循標準的 BST 刪除操作。
找到 target
節點
find_free_tree()
會在 BST 中找到 target
並返回它的指標。node_ptr
是指向 target
指標的指標,方便後續修改樹的結構。判斷 target
節點的子節點情況
Case 1: target
有兩個子節點(同時擁有左、右子樹)
predecessor
,就是 target
左子樹中最大(最右)的節點assert()
來做驗證 find_predecessor_free_tree()
的,確保 pred_ptr
是正確的pred_ptr
替換 target
pred_ptr
就是 target->l
target
pred_ptr
在 target->l
更深的位置
target
Case 2: target
只有一個子節點
直接用 target
唯一的子節點取代它。
Case 3: target
沒有子節點
直接刪除該節點。
清除 target
的指標
防止 target
仍然指向舊的子節點,避免潛在的 dangling pointer 問題。
範例:
Case 1:20
沒有子節點,直接刪除。
Case 2:30
只有一個右子節點 (40
),讓 40
直接取代 30
。
Case 3:50
有左右子節點,找前驅節點 = 40
(左子樹的最大值)。用 40
替換 50
,然後刪除 40
參閱 memory-allocators,針對你補完 (及後續改進) 的記憶體配置器,進行效能評比,並解讀其效能表現
3
解釋上述程式碼運作原理
研讀〈A comparative study of linked list sorting algorithms〉,分析針對雙向鏈結串列的排序演算法考量,並運用 Linux 核心風格的 List API 予以實作
1
Stable Sorting:在排序過程中,若兩個元素的值相同,它們的原始相對順序不會改變。
此程式碼的核心概念是 「分割」+「遞迴排序」+「合併」,假設我們有以下的鏈結串列:
選擇基準點(Pivot)
list_first_entry(head, struct listitem, list)
取得 head
串列中的第一個節點作為基準點 pivot
。
list_del(&pivot->list);
從 head
中移除 pivot
。
走訪串列並分割到 list_less
和 list_greater
遍歷 head 中的所有元素:
item
的數值 小於 pivot,則將 item
移動到 list_less
。
item
移動到 list_greater
。
使用 list_quicksort
函式,遞迴對 list_less
和 list_greater
進行排序
list_less
list_greater
合併排序後的結果
pivot
放回 head
:list_add(&pivot->list, head);
將 pivot
放到 head
串列的開頭list_less
(已排序)到 head
list_greater
(已排序)到 head
的尾端解釋上方程式碼運作原理,改進
random_shuffle_array
也探討list_for_each_entry_safe
建構的迴圈中,若將list_move_tail
換為list_move
,為何會無法滿足 stable sorting
將程式碼整合進 lab0 提及的
qtest
,並探討環狀雙向鏈結串列的排序效能,並善用 perf 一類的效能分析工具,探討包含 Linux 核心 list_sort 在內排序實作的效能表現並充分解釋
2
二進位系統:
從最高位元開始,依序決定 是 1 或 0
範例:
,故
,故
,故
clz2()
:計算一個 32 位元整數的 leading zeros,即數字前面有多少個 0。
範例分析:
第一次遞迴(c == 0)
upper = 0x0000
,取高 16-bitlower = 0x03F0
,取低 16-bitupper == 0
,所以走:第二次遞迴(c == 1)
upper = 0x0003
,取高 24-bitlower = 0x00F0
,取低 8-bitupper != 0
,所以走:第三次遞迴(c == 2)
upper = 0x0000
,取高 28-bitlower = 0x0003
,取低 4-bitupper == 0
,所以:第四次遞迴(c == 3)
upper = 0x0000
,取高 30-bitlower = 0x0003
,取低 2-bitupper == 0
,所以:回推回去:
結果是 22 個前導零。
clz64()
計算 64 位元 leading zeros
clz32()
。clz32()
,並加上 32。sqrti()
:整數開平方根
參考 <rota1001>
的作法,以改進程式的觀點來理解程式碼。
平方根暫存
定義
其中 是 或 ,取決於第 個位元是不是 。
假設 ,則
可推得一般式
因此 的更新式可寫為
對應程式碼為 p += (1 << i)
檢查條件
每次更新都檢查 有沒有成立,令
又
則可以改成檢查:
左右消去 得
對應程式碼為 x >= b
,其中 b = (p << (i + 1)) + (1 << (2 * i))
待處理數的更新
對應程式碼為 x -= b
此部分的完整程式碼為:
假設新的變數
平方根暫存
同乘
則可以改寫成
對應程式碼為 y = (y >> 1) + (1 << (2 * i))
檢查條件
對應程式碼為 x >= b
,其中 b = y + (1 << (2 * i))
此部分的完整程式碼為:
將 (1 << (2 * i))
替換成 m
clz64(x)
:計算 x
前導零的數量total_bits - 1 - clz64(x)
:取得 x
最高有效位的索引& ~1
:將數值的最右邊一位 (最低位) 清零,確保結果為偶數m = 1ULL << shift
:將 m
的值令為
1ULL
:代表 1,且是 uint64_t
類型 (unsigned long long)shift
:是一個偶數,確保 m
是 4 的次方數(即 1, 4, 16, …)解釋上述程式碼,並探討擴充為 (向上取整數) 實作,該如何調整並保持只用整數加減及位元操作
第一次實作:
上面的程式碼無法正確處理 ceil_sqrti(64)
的狀況
原因是 x
已經不是最初的值,因此使用 y*y != x
來判斷會發生錯誤。作以下改變即可得到正確答案:
然而,原本的 sqrti 「沒有」用到乘法,因此改寫過的程式碼也「不該用乘法」。
因為 的更新式為 ,故若 為完全平方數,最終值會是 。判斷式則改為:
如此一來即可避免使用成本較高的乘法計算。
參照計算機結構測驗題 C 及其注釋,以 C 語言 (搭配 GNU extension) 撰寫不依賴除法指令的
sqrtf
,確保符合 IEEE 754 規格。對照 sqrtf, rsqrt, reciprocal implementations in Arm assembly
參照[從 √2 的存在談開平方根](從 √2 的存在談開平方根的快速運算)的快速運算提到的「藉由查表的平方根運算」,以 C 語言實作,並比較不同手法的整數開平方根效能。一旦發現效能改進的機會,可準備提交改進
int_sqrt
程式碼到 Linux 核心
3
實作 Two Sum
使用 hash table (以下簡稱 HT) 記錄缺少的那一個值 (即 target - nums[i]
) 和對應的索引。考慮以下案例:
Index i |
nums[i] |
target - nums[i] |
HT 是否有 nums[i] ? |
操作 | HT 狀態 |
---|---|---|---|---|---|
0 | 2 | 9-2=7 | No | 加入 HT[7] = 0 |
{ 7:0 } |
1 | 11 | 9-11=-2 | No | 加入 HT[-2] = 1 |
{ 7: 0, -2: 1 } |
2 | 7 | 9-7=2 | Yes | 回傳 [2, HT[7]] = [2, 0] |
{ 7: 0, -2: 1 } |
參考 Linux 原始碼中 type.h :
hlist_head
只有 first
,節省空間hlist_node
使用 pprev
(指向指標的指標)find_key()
:在 hash table 中查找 key
hash()
計算 key 應該存放的 bucket(即 hlist_head
)。map->ht
是 哈希表的 bucket 陣列,透過 hash()
計算索引,取得對應的 hlist_head *head
。p
只是 hlist_node
,但 hash_key
是:
map_get()
:查找 key 並返回 data
map_add()
:將 (key, data)
新增至哈希表 (map_t
) 中
h
: key 應該存放的 bucketn
: 新節點 (hlist_node
)first
: 當前 bucket (hlist_head
) 的第一個節點Graphviz 練習:
新增 test.dot
$ dot -Tpng test.dot -o output.png
(To be done…)
10
:哈希表的 bit 數,意即 bucket 大小為 ret
:用來存放找到的索引值,最多只有兩個數,所以分配 2 個整數大小的記憶體ret[0] = i
:目前數字的索引ret[1] = *p
:匹配數的索引,來自哈希表解釋上述程式碼運作原理,應提供測試程式碼
針對 Linux 核心的 hash table 實作,提供更多圖例並揣摩 Linux 核心開發者
進行《The Art of Computer Programming》(vol 3) 一書section 6.4 的 exercise 8,也就是證明 Theorem S
注意: Linux 核心內部大量使用 hash table,但並非每處的雜湊函數都滿足減少碰撞及避免 clustering 現象的原則,也就是存在若干改進空間
探討 Linux 核心中使用 hash table 的應用案例,至少二項,應當列出原始程式碼並分析,斟酌搭配 git log 以得知 Linux 核心開發者變更程式碼的考量因素