liangchingyun
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee
  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2025q1 Homework2 (quiz1+2) contributed by < `liangchingyun` > ## 2025q1 第 1 週測驗題 ### 測驗 [`1`](https://hackmd.io/@sysprog/linux2025-quiz1#%E6%B8%AC%E9%A9%97-1) #### 延伸問題 1 > 解釋上方程式碼運作原理 --- 巨集定義 ```c #define my_assert(test, message) \ do { \ if (!(test)) \ return message; \ } while (0) #define my_run_test(test) \ do { \ char *message = test(); \ tests_run++; \ if (message) \ return message; \ } while (0) ``` * `my_assert()` : 如果測試條件 `test` 為 `false`,則返回 `message`。 * `my_run_test()` : 若測試失敗,則直接回傳錯誤訊息。 --- 重設鏈結串列 ```c static list_t *list_reset(void) { for (size_t i = 0; i < N; i++) { items[i].value = i; items[i].next = NULL; } l.head = NULL; return &l; } ``` * 將 `items[i]` 的 `value` 設為 `i`,並將 `next` 設為 `NULL`。 * `l.head = NULL`; 清空鏈結串列。 --- 測試函式 `test_list()` 包含三種測試情境 1. 在開頭插入 ```c /* Test inserting at the beginning */ list_reset(); my_assert(list_size(&l) == 0, "Initial list size is expected to be zero."); for (size_t i = 0; i < N; i++) list_insert_before(&l, l.head, &items[i]); my_assert(list_size(&l) == N, "Final list size should be N"); size_t k = N - 1; list_item_t *cur = l.head; while (cur) { my_assert(cur->value == k, "Unexpected list item value"); k--; cur = cur->next; } ``` * `l.head` 表示插入在最前面。 * 逐個檢查 `cur->value` 是否符合預期的逆序 (從 `N-1` 到 `0`)。 2. 在結尾插入 ```c /* Test inserting at the end */ list_reset(); my_assert(list_size(&l) == 0, "Initial list size is expected to be zero."); for (size_t i = 0; i < N; i++) list_insert_before(&l, NULL, &items[i]); my_assert(list_size(&l) == N, "Final list size should be N"); k = 0; cur = l.head; while (cur) { my_assert(cur->value == k, "Unexpected list item value"); k++; cur = cur->next; } ``` * `NULL` 表示插入在尾端 * 逐個檢查 `cur->value` 是否符合預期的正序 (從 `0` 到 `N-1`)。 3. 重設串列並插入 ```c /* Reset the list and insert elements in order (i.e. at the end) */ list_reset(); my_assert(list_size(&l) == 0, "Initial list size is expected to be zero."); for (size_t i = 0; i < N; i++) list_insert_before(&l, NULL, &items[i]); my_assert(list_size(&l) == N, "list size should be N"); ``` 這部分與 2. 類似,但主要是確認插入結果仍然正確。 --- 測試執行 ```c int tests_run = 0; static char *test_suite(void) { my_run_test(test_list); return NULL; } ``` `test_suite()` 執行 `test_list()`,並計算測試數量。 --- 主函式 ```c int main(void) { printf("---=[ List tests\n"); char *result = test_suite(); if (result) printf("ERROR: %s\n", result); else printf("ALL TESTS PASSED\n"); printf("Tests run: %d\n", tests_run); return !!result; } ``` * `test_suite()` 若發生錯誤,則 `result` 為錯誤訊息。 * 測試通過則顯示 `ALL TESTS PASSED`,否則輸出 `ERROR` 訊息。 --- `list_insert_before` 函式: ```c static inline void list_insert_before(list_t *l, list_item_t *before, list_item_t *item); { list_item_t **p; for (p = l->head; *p != before; p = &(*p)->next) ; *p = item; item->next = before; } ``` 以 `before = B` 為例: 1. `p` 初始指向鏈結串列的頭指標。 ```graphviz digraph G { rankdir=LR; node [shape=record]; head [label="head"]; A [label="A"]; B [label="B"]; C [label="C"]; D [label="D"]; p [label="p"]; head -> A; A -> B; B -> C; C -> D; p -> head; } ``` 2. `for` 迴圈會遍歷鏈結串列,直到找到 `before` 節點。 ```graphviz digraph G { rankdir=LR; node [shape=record]; head [label="head"]; A [label="A"]; B [label="B"]; C [label="C"]; D [label="D"]; p [label="p"]; head -> A; A -> B; B -> C; C -> D; p -> A[color=red]; } ``` ```graphviz digraph G { rankdir=LR; node [shape=record]; head [label="head"]; A [label="A"]; B [label="B"]; C [label="C"]; D [label="D"]; p [label="p"]; head -> A; A -> B; B -> C; C -> D; p -> B[color=red]; } ``` 3. `*p = item;` 將前一個節點的 `next` 指向 `item`,完成插入。 ```graphviz digraph G { rankdir=LR; node [shape=record]; head [label="head"]; X [label="X"]; A [label="A"]; B [label="B"]; C [label="C"]; D [label="D"]; p [label="p"]; head -> A; A -> B[style=dashed]; B -> C; C -> D; p -> B; A -> X[color=red]; } ``` 4. `item->next = before;` 讓 `item` 指向 `before`,確保鏈結不會斷開。 ```graphviz digraph G { rankdir=LR; node [shape=record]; head [label="head"]; X [label="X"]; A [label="A"]; B [label="B"]; C [label="C"]; D [label="D"]; p [label="p"]; head -> A; B -> C; C -> D; p -> B; A -> X; X -> B[color=red]; } ``` #### 延伸問題 2 > 在現有的程式碼基礎上,撰寫合併排序操作 ### 測驗 [`2`](https://hackmd.io/@sysprog/linux2025-quiz1#%E6%B8%AC%E9%A9%97-2) #### 延伸問題 1 > 解釋上方程式碼運作的原理,並嘗試補完程式碼,使其可獨立執行,應提供相關的測試程式碼 這段程式碼的功能是從二元搜尋樹 (BST) 中移除特定的節點,這個 BST 用於管理記憶體分配中的「空閒區塊 (free blocks)」。 --- 結構體 ```c typedef struct block { size_t size; struct block *l, *r; } block_t; ``` `block_t` 代表一個記憶體區塊,具有: * `size`:區塊大小 * `l`:指向左子節點(較小的區塊) * `r`:指向右子節點(較大的區塊) --- `remove_free_tree` 用來從 BST 中移除 `target` 節點,遵循標準的 BST 刪除操作。 1. 找到 `target` 節點 ```c /* Locate the pointer to the target node in the tree. */ block_t **node_ptr = find_free_tree(root, target); ``` * `find_free_tree()` 會在 BST 中找到 `target` 並返回它的指標。 * `node_ptr` 是指向 `target` 指標的指標,方便後續修改樹的結構。 2. 判斷 `target` 節點的子節點情況 * Case 1: `target` 有兩個子節點(同時擁有左、右子樹) ```c /* If the target node has two children, we need to find a replacement. */ if ((*node_ptr)->l && (*node_ptr)->r) { /* Find the in-order predecessor: * This is the rightmost node in the left subtree. */ block_t **pred_ptr = &(*node_ptr)->l; while (EEEE) pred_ptr = FFFF; /* Verify the found predecessor using a helper function (for debugging). */ block_t *expected_pred = find_predecessor_free_tree(root, *node_ptr); assert(expected_pred == *pred_ptr); /* If the predecessor is the immediate left child. */ if (*pred_ptr == (*node_ptr)->l) { block_t *old_right = (*node_ptr)->r; *node_ptr = *pred_ptr; /* Replace target with its left child. */ (*node_ptr)->r = old_right; /* Attach the original right subtree. */ assert(*node_ptr != (*node_ptr)->l); assert(*node_ptr != (*node_ptr)->r); } else { /* The predecessor is deeper in the left subtree. */ block_t *old_left = (*node_ptr)->l; block_t *old_right = (*node_ptr)->r; block_t *pred_node = *pred_ptr; /* Remove the predecessor from its original location. */ remove_free_tree(&old_left, *pred_ptr); /* Replace the target node with the predecessor. */ *node_ptr = pred_node; (*node_ptr)->l = old_left; (*node_ptr)->r = old_right; assert(*node_ptr != (*node_ptr)->l); assert(*node_ptr != (*node_ptr)->r); } } ``` 1. 找到前驅節點 `predecessor` ,就是 `target` 左子樹中最大(最右)的節點 2. 使用 `assert()` 來做驗證 `find_predecessor_free_tree()` 的,確保 `pred_ptr` 是正確的 3. 用 `pred_ptr` 替換 `target` * 如果 `pred_ptr` 就是 `target->l` * 用前驅節點取代 `target` * 接回原本的右子樹 * 如果 `pred_ptr` 在 `target->l` 更深的位置 * 移除前驅節點 * 用前驅節點取代 `target` * Case 2: `target` 只有一個子節點 ```c block_t *child = ((*node_ptr)->l) ? (*node_ptr)->l : (*node_ptr)->r; *node_ptr = child; ``` 直接用 `target` 唯一的子節點取代它。 * Case 3: `target` 沒有子節點 ```c *node_ptr = NULL; ``` 直接刪除該節點。 3. 清除 `target` 的指標 ```c /* Clear the removed node's child pointers to avoid dangling references. */ target->l = NULL; target->r = NULL; ``` 防止 `target` 仍然指向舊的子節點,避免潛在的 dangling pointer 問題。 範例: ``` [50] / \ [30] [70] / \ / \ [20] [40][60] [80] ``` Case 1:`20` 沒有子節點,直接刪除。 ``` [50] / \ [30] [70] \ / \ [40] [60] [80] ``` Case 2:`30` 只有一個右子節點 (`40`),讓 `40` 直接取代 `30`。 ``` [50] / \ [40] [70] / \ [60] [80] ``` Case 3:`50` 有左右子節點,找前驅節點 = `40`(左子樹的最大值)。用 `40` 替換 `50`,然後刪除 `40` ``` [40] \ [70] / \ [60] [80] ``` #### 延伸問題 2 > 參閱 [memory-allocators](https://github.com/mtrebi/memory-allocators),針對你補完 (及後續改進) 的記憶體配置器,進行效能評比,並解讀其效能表現 ### 測驗 [`3`](https://hackmd.io/@sysprog/linux2025-quiz1?stext=8296%3A5%3A0%3A1742717039%3AaNQwCv) #### 延伸問題 1 > 解釋上述程式碼運作原理 #### 延伸問題 2 > 研讀〈[A comparative study of linked list sorting algorithms](https://dl.acm.org/doi/pdf/10.1145/225890.225893)〉,分析針對雙向鏈結串列的排序演算法考量,並運用 Linux 核心風格的 List API 予以實作 ## 2025q1 第 2 週測驗題 ### 測驗 [`1`](https://hackmd.io/@sysprog/linux2025-quiz2?stext=497%3A5%3A0%3A1742053087%3AvGUhXy) Stable Sorting:在排序過程中,若兩個元素的值相同,它們的原始相對順序不會改變。 ```c { struct list_head list_less, list_greater; struct listitem *pivot; struct listitem *item = NULL, *is = NULL; if (list_empty(head) || list_is_singular(head)) return; INIT_LIST_HEAD(&list_less); INIT_LIST_HEAD(&list_greater); pivot = list_first_entry(head, struct listitem, list); list_del(&pivot->list); list_for_each_entry_safe (item, is, head, list) { if (cmpint(&item->i, &pivot->i) < 0) list_move_tail(&item->list, &list_less); else list_move_tail(&item->list, &list_greater); } list_quicksort(&list_less); list_quicksort(&list_greater); list_add(&pivot->list, head); list_splice(&list_less, head); list_splice_tail(&list_greater, head); } ``` 此程式碼的核心概念是 「分割」+「遞迴排序」+「合併」,假設我們有以下的鏈結串列: ```c [ 5 ] -> [ 3 ] -> [ 8 ] -> [ 2 ] -> [ 7 ] ``` 1. 選擇基準點(Pivot) * `list_first_entry(head, struct listitem, list)` 取得 `head` 串列中的第一個節點作為基準點 `pivot`。 ```c pivot = 5 ``` * `list_del(&pivot->list);` 從 `head` 中移除 `pivot`。 ```c [ 3, 8, 2, 7 ] ``` 2. 走訪串列並分割到 `list_less` 和 `list_greater` 遍歷 head 中的所有元素: * 如果 `item` 的數值 小於 pivot,則將 `item` 移動到 `list_less`。 ```c list_less = [ 3, 2 ] ``` * 否則,將 `item` 移動到 `list_greater`。 ```c list_greater = [ 8, 7 ] ``` 3. 使用 `list_quicksort` 函式,遞迴對 `list_less` 和 `list_greater` 進行排序 * 遞迴排序 `list_less` ```c Pivot = 3 list_less = [2] list_greater = [] ``` * 遞迴排序 `list_greater` ```c Pivot = 8 list_less = [7] list_greater = [] ``` 4. 合併排序後的結果 * 將 `pivot` 放回 `head`: `list_add(&pivot->list, head);` 將 `pivot` 放到 `head` 串列的開頭 * 合併 `list_less`(已排序)到 `head` * 合併 `list_greater`(已排序)到 `head` 的尾端 ```c [ 2 ] -> [ 3 ] -> [ 5 ] -> [ 7 ] -> [ 8 ] ``` #### 延伸問題 1 > 解釋上方程式碼運作原理,改進 `random_shuffle_array` 也探討 `list_for_each_entry_safe` 建構的迴圈中,若將 `list_move_tail` 換為 `list_move`,為何會無法滿足 stable sorting #### 延伸問題 2 > 將程式碼整合進 [lab0](https://hackmd.io/@sysprog/linux2025-lab0) 提及的 `qtest`,並探討環狀雙向鏈結串列的排序效能,並善用 [perf](https://hackmd.io/@sysprog/linux-perf) 一類的效能分析工具,探討包含 Linux 核心 [list_sort](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 在內排序實作的效能表現並充分解釋 ### 測驗 [`2`](https://hackmd.io/@sysprog/linux2025-quiz2?stext=4504%3A5%3A0%3A1742130777%3AeEZNJj) #### 理論分析 > [參考](http://hackmd.io/l4--gpsZQBiEI5LKS1lDvg?view#%E6%AA%A2%E8%A8%8E%E7%AC%AC%E4%BA%8C%E9%80%B1%E6%B8%AC%E9%A9%97%E9%A1%8C) 二進位系統: $(b_nb_{n-1}\dots b_1b_0)_2^2 = (b_n\times 2^n + b_{n-1}\times 2^{n-1}+\dots b_ 1\times2^{1} +b_0\times2^0)^2$ 從最高位元開始,依序決定 $b_i$ 是 1 或 0 範例: $(63)_{10} = (011111)_2 = (b_2\times 2^2 + b_{1}\times 2^{1}+b_0\times2^0)^2$ $(4)^2 < 63$,故 $b_2 = 1$ $(4 + 2)^2 < 63$,故 $b_1 = 1$ $(4 + 2 + 1)^2 < 63$,故 $b_0 = 1$ --- #### 程式實作 - clz `clz2()`:計算一個 32 位元整數的 leading zeros,即數字前面有多少個 0。 ```c static const int mask[] = {0, 8, 12, 14}; static const int magic[] = {2, 1, 0, 0}; unsigned clz2(uint32_t x, int c) { if (!x && !c) return 32; uint32_t upper = (x >> (16 >> c)); uint32_t lower = (x & (0xFFFF >> mask[c])); if (c == 3) return upper ? magic[upper] : 2 + magic[lower]; return upper ? clz2(upper, c + 1) : (16 >> (c)) + clz2(lower, c + 1); } ``` 範例分析: ```c clz2(0x000003F0, 0) ``` **第一次遞迴(c == 0)** ```c upper = (x >> (16 >> 0)); // x >> 16 lower = (x & (0xFFFF >> mask[0])); // x & 0xFFFF ``` * `upper = 0x0000`,取高 16-bit * `lower = 0x03F0`,取低 16-bit * `upper == 0`,所以走: ```c (16 >> 0) + clz2(lower, 1) // 16 + clz2(0x03F0, 1) ``` **第二次遞迴(c == 1)** ```c upper = (x >> (16 >> 1)); // x >> 8 lower = (x & (0xFFFF >> mask[1])); // x & (0xFFFF >> 8) ``` * `upper = 0x0003`,取高 24-bit * `lower = 0x00F0`,取低 8-bit * `upper != 0`,所以走: ```c clz2(upper, 2) // clz2(0x0003, 2) ``` **第三次遞迴(c == 2)** ```c upper = (x >> (16 >> 2)); // x >> 4 lower = (x & (0xFFFF >> mask[2])); // x & (0xFFFF >> 12) ``` * `upper = 0x0000`,取高 28-bit * `lower = 0x0003`,取低 4-bit * `upper == 0`,所以: ```c (16 >> 2) + clz2(lower, 3) // 4 + clz2(0x0003, 3) ``` **第四次遞迴(c == 3)** ```c upper = (x >> (16 >> 3)); // x >> 2 lower = (x & (0xFFFF >> mask[3])); // x & (0xFFFF >> 14) ``` * `upper = 0x0000`,取高 30-bit * `lower = 0x0003`,取低 2-bit * `upper == 0`,所以: ```c return 2 + magic[0x0003] // magic[3] = 0 ``` 回推回去: ```c clz2(0x000003F0, 0) = 16 + 4 + 2 = 22 ``` 結果是 22 個前導零。 --- `clz64()` 計算 64 位元 leading zeros * 如果 x 的高 32 位元不為 0,就計算高 32 位的 `clz32()`。 * 若 x 的高 32 位元為 0,則計算低 32 位元的 `clz32()`,並加上 32。 ```c #define clz32(x) clz2(x, 0) static inline int clz64(uint64_t x) { /* If the high 32 bits are nonzero, count within them. * Otherwise, count in the low 32 bits and add 32. */ return (x >> 32) ? clz32((uint32_t) (x >> 32)) : clz32((uint32_t) x) + 32; } ``` --- #### 程式實作 - sqrti `sqrti()`:整數開平方根 參考 [`<rota1001>`](https://hackmd.io/@rota1001/linux2025-homework2#%E6%95%B4%E6%95%B8%E9%96%8B%E5%B9%B3%E6%96%B9%E6%A0%B9) 的作法,以改進程式的觀點來理解程式碼。 ##### 初版程式 1. 平方根暫存 定義 $$p_i=\sum^{\frac{\text{shift}}{2}}_{j=i}a_j$$ 其中 $a_j$ 是 $2^j$ 或 $0$,取決於第 $j$ 個位元是不是 $1$。 假設 $\text{shift} = 4$,則 \begin{align} &p_2 = a_2 \\ &p_1 = a_1 + a_2 = p_2 + a_1 \\ &p_0 = a_0 + a_1 + a_2 = p_1 + a_0 \end{align} 可推得一般式 $$p_i = p_{i+1} + a_i$$ 因此 $p$ 的更新式可寫為 $$p_i = p_{i+1} + 2^i$$ 對應程式碼為 `p += (1 << i)` 2. 檢查條件 每次更新都檢查 $N\geq p_i^2$ 有沒有成立,令 $$x_{i+1} = N - p_{i+1}^2$$ 又 $$p_i^2 = (p_{i+1} + 2^i)^2 = p_{i+1}^2 + 2^{i+1}\cdot p_{i+1} + 2^{2i}$$ 則可以改成檢查: $$x_{i+1} + p_{i+1}^2\geq p_{i+1}^2 + 2^{i+1}\cdot p_{i+1} + 2^{2i}$$ 左右消去 $p_{i+1}^2$ 得 $$x_{i+1}\geq 2^{i+1}\cdot p_{i+1} + 2^{2i}$$ 對應程式碼為 `x >= b`,其中 `b = (p << (i + 1)) + (1 << (2 * i))` 3. 待處理數的更新 \begin{align} x_{i} &= N - p_{i}^2 \\ &= N - p_{i+1}^2 - 2^{i+1}\cdot p_{i+1} - 2^{2i} \\ &= x_{i+1} - (2^{i+1}\cdot p_{i+1} + 2^{2i}) \end{align} 對應程式碼為 `x -= b` 此部分的完整程式碼為: ```c uint64_t sqrti_simple(uint64_t x) { uint64_t p = 0; if (x <= 1) return x; int total_bits = 64; int shift = (total_bits - 1 - clz64(x)) & ~1; for(int i = (shift / 2); i >= 0; i--) { uint64_t b = (p << (i + 1)) + (1 << (2 * i)); if (x >= b) { x -= b; p += (1 << i); } } return p; } ``` ##### 改進位移運算 假設新的變數 $$y_i = p_i \times 2^i$$ 1. 平方根暫存 $$p_i = p_{i+1} + 2^i$$ 同乘 $2^i$ $$2^ip_i=2^ip_{i+1}+2^{2i}$$ 則可以改寫成 $$y_i=2^{-1}y_{i+1}+2^{2i}$$ 對應程式碼為 `y = (y >> 1) + (1 << (2 * i))` 2. 檢查條件 $$x_{i+1}\geq 2^{i+1}\cdot p_{i+1} + 2^{2i} = y_{i+1} + 2^{2i}$$ 對應程式碼為 `x >= b`,其中 `b = y + (1 << (2 * i))` 此部分的完整程式碼為: ```c uint64_t sqrti_simple(uint64_t x) { uint64_t y = 0; if (x <= 1) return x; int total_bits = 64; int shift = (total_bits - 1 - clz64(x)) & ~1; for(int i = (shift / 2); i >= 0; i--) { uint64_t b = y + (1 << (2 * i)); y >>= 1; if (x >= b) { x -= b; y += (1 << (2 * i)); } } return y; } ``` ##### 改寫成原題目的程式碼 將 `(1 << (2 * i))` 替換成 `m` ```c uint64_t sqrti(uint64_t x) { uint64_t m, y = 0; if (x <= 1) return x; int total_bits = 64; int shift = (total_bits - 1 - clz64(x)) & ~1; m = 1ULL << shift; while(m) { uint64_t b = y + m; y >>= 1; if (x >= b) { x -= b; y += m; } m >>= 2; } return y; } ``` * `clz64(x)` :計算 `x` 前導零的數量 * `total_bits - 1 - clz64(x)` :取得 `x` 最高有效位的索引 * `& ~1` :將數值的最右邊一位 (最低位) 清零,確保結果為偶數 * `m = 1ULL << shift` :將 `m` 的值令為 $2^{\text{shift}}$ * `1ULL`:代表 1,且是 `uint64_t` 類型 (unsigned long long) * `shift`:是一個偶數,確保 `m` 是 4 的次方數(即 1, 4, 16, ...) #### 延伸問題 1 >解釋上述程式碼,並探討擴充為 $\lceil \sqrt{x}) \rceil$ (向上取整數) 實作,該如何調整並保持只用整數加減及位元操作 第一次實作: ```c uint64_t ceil_sqrti(uint64_t x) { uint64_t m, y = 0; if (x <= 1) return x; int total_bits = 64; int shift = (total_bits - 1 - clz64(x)) & ~1; m = 1ULL << shift; while (m) { uint64_t b = y + m; y >>= 1; if (x >= b) { x -= b; y += m; } m >>= 2; if (y*y != x) y += 1; } return y; } ``` 上面的程式碼無法正確處理 `ceil_sqrti(64)` 的狀況 ``` ceil_sqrti(64) = 9 ``` 原因是 `x` 已經不是最初的值,因此使用 `y*y != x` 來判斷會發生錯誤。作以下改變即可得到正確答案: ```diff uint64_t ceil_sqrti(uint64_t x) { + uint64_t origin = x; ... - if (y*y != x) + if (y*y != origin) y += 1; } return y; } ``` ``` ceil_sqrti(64) = 8 ``` 然而,原本的 sqrti 「沒有」用到乘法,因此改寫過的程式碼也「不該用乘法」。 因為 $x$ 的更新式為 $x_{i} = N - p_{i}^2$,故若 $x$ 為完全平方數,最終值會是 $0$ 。判斷式則改為: ```diff uint64_t ceil_sqrti(uint64_t x) { ... - if (y*y != origin) + if (x != 0) y += 1; } return y; } ``` 如此一來即可避免使用成本較高的乘法計算。 #### 延伸問題 2 >參照[計算機結構測驗題 C](https://hackmd.io/@sysprog/arch2024-quiz3-sol#Problem-C) 及其[注釋](https://hackmd.io/@sysprog/HJKRbSAVJl#Problem-C30),以 C 語言 (搭配 GNU extension) 撰寫不依賴除法指令的 `sqrtf`,確保符合 IEEE 754 規格。對照 [sqrtf, rsqrt, reciprocal implementations in Arm assembly](https://github.com/picolibc/picolibc/issues/956) #### 延伸問題 3 >參照[從 √2 的存在談開平方根](從 √2 的存在談開平方根的快速運算)的快速運算提到的「藉由查表的平方根運算」,以 C 語言實作,並比較不同手法的整數開平方根效能。一旦發現效能改進的機會,可準備提交改進 `int_sqrt` 程式碼到 Linux 核心 ### 測驗 [`3`](https://hackmd.io/@sysprog/linux2025-quiz2?stext=7569%3A5%3A0%3A1742185627%3AeBp3ey) > [Linux 核心的 hash table 實作](https://hackmd.io/@sysprog/linux-hashtable) 實作 [Two Sum](https://leetcode.com/problems/two-sum/) 使用 [hash table](https://en.wikipedia.org/wiki/Hash_table) (以下簡稱 HT) 記錄缺少的那一個值 (即 `target - nums[i]`) 和對應的索引。考慮以下案例: ```c nums = [2, 11, 7, 15] target = 9 ``` | 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](https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/types.h#L194-L204) : ```c struct list_head { struct list_head *next, *prev; }; struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; }; ``` * `hlist_head` 只有 `first`,節省空間 * `hlist_node` 使用 `pprev`(指向指標的指標) #### 檢索程式碼 1. `find_key()`:在 hash table 中查找 key ```c static struct hash_key *find_key(map_t *map, int key) { struct hlist_head *head = &(map->ht)[hash(key, map->bits)]; // 遍歷 bucket 中的 hlist 鏈結串列 for (struct hlist_node *p = head->first; p; p = p->next) { struct hash_key *kn = container_of(p, struct hash_key, node); // 比對 key,找到則返回 kn if (kn->key == key) return kn; } return NULL; } ``` * 使用 `hash()` 計算 key 應該存放的 bucket(即 `hlist_head`)。 * `map->ht` 是 哈希表的 bucket 陣列,透過 `hash()` 計算索引,取得對應的 `hlist_head *head`。 * `p` 只是 `hlist_node`,但 `hash_key` 是: ```c struct hash_key { int key; void *data; struct hlist_node node; }; ``` 2. `map_get()`:查找 key 並返回 data ```c void *map_get(map_t *map, int key) { struct hash_key *kn = find_key(map, key); //利用 find_key() 找到 key return kn ? kn->data : NULL; // 如果 kn 存在,返回 kn->data,否則返回 NULL } ``` #### 新增操作 `map_add()`:將 `(key, data)` 新增至哈希表 (`map_t`) 中 ```c void map_add(map_t *map, int key, void *data) { // 檢查 key 是否已存在(避免重複) struct hash_key *kn = find_key(map, key); if (kn) return; // 分配記憶體 儲存新的 (key, data) kn = malloc(sizeof(struct hash_key)); kn->key = key, kn->data = data; struct hlist_head *h = &map->ht[hash(key, map->bits)]; // 取得對應的 bucket struct hlist_node *n = &kn->node, *first = h->first; // 新節點 // 插入新節點 n->next = first; if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first; } ``` * `h`: key 應該存放的 bucket * `n`: 新節點 (`hlist_node`) * `first`: 當前 bucket (`hlist_head`) 的第一個節點 Graphviz 練習: > 新增 test.dot > `$ dot -Tpng test.dot -o output.png` (To be done...) #### 釋放記憶體的操作 ```c void map_deinit(map_t *map) { if (!map) return; for (int i = 0; i < MAP_HASH_SIZE(map->bits); i++) { struct hlist_head *head = &map->ht[i]; for (struct hlist_node *p = head->first; p;) { struct hash_key *kn = container_of(p, struct hash_key, node); struct hlist_node *n = p; p = p->next; if (!n->pprev) /* unhashed */ goto bail; struct hlist_node *next = n->next, **pprev = n->pprev; *pprev = next; if (next) next->pprev = pprev; n->next = NULL, n->pprev = NULL; bail: free(kn->data); free(kn); } } free(map); } ``` #### 主體程式碼 ```c int *twoSum(int *nums, int numsSize, int target, int *returnSize) { map_t *map = map_init(10); *returnSize = 0; int *ret = malloc(sizeof(int) * 2); // 如果 malloc 失敗,則跳轉到 bail 進行清理 if (!ret) goto bail; // 走訪 nums ,查找匹配的數 for (int i = 0; i < numsSize; i++) { int *p = map_get(map, target - nums[i]); if (p) { /* found, 其索引存於 p */ ret[0] = i, ret[1] = *p; *returnSize = 2; break; } // 若 target - nums[i] 不在哈希表中,則存入 nums[i] p = malloc(sizeof(int)); *p = i; map_add(map, nums[i], p); } bail: map_deinit(map); // 清理哈希表,釋放記憶體 return ret; } ``` * `10`:哈希表的 bit 數,意即 bucket 大小為 $2^{10} = 1024$ * `ret`:用來存放找到的索引值,最多只有兩個數,所以分配 2 個整數大小的記憶體 * `ret[0] = i`:目前數字的索引 * `ret[1] = *p`:匹配數的索引,來自哈希表 #### 延伸問題 1 > 解釋上述程式碼運作原理,應提供測試程式碼 針對 [Linux 核心的 hash table 實作](https://hackmd.io/@sysprog/linux-hashtable),提供更多圖例並揣摩 Linux 核心開發者 #### 延伸問題 2 > 進行《[The Art of Computer Programming](https://www-cs-faculty.stanford.edu/~knuth/taocp.html)》(vol 3) 一書section 6.4 的 exercise 8,也就是證明 Theorem S 注意: Linux 核心內部大量使用 hash table,但並非每處的雜湊函數都滿足減少碰撞及避免 clustering 現象的原則,也就是存在若干改進空間 #### 延伸問題 3 > 探討 Linux 核心中使用 hash table 的應用案例,至少二項,應當列出原始程式碼並分析,斟酌搭配 git log 以得知 Linux 核心開發者變更程式碼的考量因素

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    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.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully