# 2025q1 Homework1 (ideas) contributed by < [`charliechiou`](https://github.com/charliechiou?tab=repositories) > ## [針對鏈結串列的資料排序改進](https://hackmd.io/@sysprog/BJPBcQ34C) 目標 : 嘗試引入 Timsort 及其延伸的演算法並與 listsort 比較。 初步閱讀這份期末專題,以下是目前的疑惑 - Timsort 實做方法及延伸演算法 - 龐大的 linux kernel程式碼應該如何去找出改動程式碼時候會影響到的內容 - 如何編譯及測試 linux kernel 中的程式碼並改良內部的程式 - 比較排序演算法時需要分析的參數 ### Timsort 演算法 先將需要排序的資料整個掃過一遍,並將單調遞增/遞減的資料視為一個 run 並依照需求先把每個 run 排序好再合併,而每個 run 之間的資料透過 stack 維持。 > 疑惑 : > - 若需要排序的資料極大,先掃描過一次不會降低效率嗎 ? > - 為何使用 stack ? > > 想法 : > - Timsort 演算法的時間複雜度為 O(nlogn) ,這樣的掃描僅花費 O(n) 因此不會特別降低效率,且這部份的掃描能夠大幅減少後面所需要的時間。 > - stack 有後進先出的特性(LIFO),合併需要存取最上面的幾個數值因此可以有效減少需要的時間 每次合併時會合併兩個 run ,因此 run 總數略大於 2 的冪時效率會特別低 (資料不平衡),為了改善平衡問題,因此長度不足於 minrun 的兩個 runs 會做兩兩合併,常見的 minrun 會設定為 64。 前述的 minrun 設定作者解釋為是由於 64 可以一次放入 cache line,但這邊是針對鍊結串列 (Linked-list) 所作的操作,因此不一定是設定為 64,是可以在探討及測試的部份。 ### adaptive_shiversSort 這部份是針對合併 minrun 所作的改良,若目前 stack 上的 run 為 X,Y,Z 且 Z 為 top ,如果 $$ \left\lfloor \log_2 X \right\rfloor \leq \left\lfloor \log_2 (\max(Y, Z)) \right\rfloor $$ 則合併 X,Y。可以很簡單的透過 clz 來實做 $log_2$ 的計算。 > 疑問: > - clz 如何很簡單的計算 $log_2$ ? > > 解答: > - 二進位中一個數字可以表示為 > $$ > N = b_k 2^k + b_{k-1} 2^{k-1} + \dots + b_1 2^1 + > $$ > 因此 $2^k$ 的 $log_2$ 會直接和最高位1的索引值對應 ### Powersort (To be done) ### 資料蒐集 作者先透過搜尋找出 linux_kernel 中有使用到 list_sort 的部份,再查看那些 function 後找到可以在使用者層級使用的命令,因此從這部份著手比較方便查看結果。 > 可以將這種方法作為改善 linux kernel 程式碼的切入點 接著使用 [virtme-ng](https://github.com/arighi/virtme-ng) 的虛擬環境來進行實驗。透過修改 list_sort 使他在被呼叫時會留下對應的紀錄,並透過 list_sort 的 kernel 版本編譯出對應的 perf tools。 > 是否這邊可以有類似 gdb 的方式來查看修改結果或呼叫結果而不用在程式碼安插一個 print ? 並透過自己撰寫的 kernel module 呼叫 list_sort 並檢視輸出 > 什麼是 kernel module 又是如何撰寫的 ? > 參考 [Linux 核心模組運作原理](/pfRzP9sxRYqYbXS6Tm0Ynw),kernel module 是一個可以載入 (load) 及卸除 (unload) 到核心的模組,詳細的使用辦法參考前述的連結。 ``` [ 0.672210] virtme-ng-init: initialization done [ 30.108569] list_sort_module: loading out-of-tree module taints kernel. [ 30.108752] List before sorting: [ 30.108754] Value: 9 [ 30.108755] Value: 8 [ 30.108755] Value: 7 [ 30.108756] Value: 6 [ 30.108757] Value: 5 [ 30.108757] Value: 4 [ 30.108758] Value: 3 [ 30.108758] Value: 2 [ 30.108759] Value: 1 [ 30.108759] Value: 0 [ 30.108760] Hello from list_sort [ 30.108761] List after sorting: [ 30.108762] Value: 0 [ 30.108762] Value: 1 [ 30.108763] Value: 2 [ 30.108763] Value: 3 [ 30.108764] Value: 4 [ 30.108764] Value: 5 [ 30.108765] Value: 6 [ 30.108766] Value: 7 [ 30.108766] Value: 8 [ 30.108767] Value: 9 ``` 接著便嘗試使用 perf 呼叫 list_sort , 這部份會需要重新編譯 `libtraceevent` 因此在 vng 為系統安裝 `libtraceevent` > libtraceevent 是 perf 依賴的 library 隨後找到正確會調用到 list_sort 的部份再參考 `metric_list_cmp` 如何將 metric_list 中要排序的資料解析出來撰寫 `dump_metric_list_ele` 將資料解析出來。 > 和先前有一樣的疑惑,是否可以透過類似 gdb 的方法省去這步驟 再接著透過 [stress-ng](https://github.com/ColinIanKing/stress-ng) 來進行壓力測試,可以測試 GPU 滿載情況的效能。但 perf 在呼叫到 list_sort 時候只依照輸入的 matric list 順序進行排序,和最後的結果,因此會是固定的資料分佈無法用作測試。 接著測試檔案系統為 ext4 的隨身碟,再透過自己撰寫的程式讓使用者輸入 ext4 裝置路徑並回傳 fsmap 內容。 > 疑問: > - ext4檔案是怎樣的檔案類型? > > 解答: > - ext4(fourth extended filesystem):[wiki](https://en.wikipedia.org/wiki/Ext4) 並透過編譯好的程式來查看目錄,但為了取得資料分佈,必須修改kernel。由於虛擬機器的檔案系統會變為 tmpfs 因此需要直接編譯完整的 kernel 直接執行。 ### 編譯 linux kernel 先下載需要的套件後,將原先電腦的 config 那來使用做為驅動的設定,再透過 make 安裝所需的 modules。重新啟動後在 grub 中選擇剛剛的 kernel 即可。 > 從這邊可以更深刻的了解到 linux 作為作業系統的存在 用類似的方式取得 XFS 的資料後便可以開始進行 sorting 的實驗。 ### 整合 Timsort及改進至 Linux 核心 作者透過 linux 中的 module 來方便的完成實做,將 list_sort 等相關算法寫成 module 後再透過 `EXPORT_SYMBOL` 來導出。 > 這部份我的理解是透過 linux 中的 module 可以把寫在不同區域的 function 打包,只要打包的 function 是在核心中運作便可以方便的使用它。 接著透過 `insmod` 將編譯好的 module 載入。 ### 實驗 實驗紀錄對同一筆資料集排序數次後的時間加總,透過 ktime_get 取得經過的時間。且為了避免 `interrupt` 以及 `preemption` 對實驗造成影響,因此作者也透過以下指令關閉 ```bash local_irq_disable(); // disable local interrupt get_cpu(); // disable preemption // Experiments local_irq_enable(); // enable local interrupt put_cpu(); // enable preemption ``` 主要測試以下幾項 - ext4 檔案系統的 ext4_getfsmap - btrfs 檔案系統的 extent_list - 透過 perf stat -M 照順序輸入所有 metric 的 perf_metric_list - xfs 檔案系統的 `xfs_buf_delwri_submit_buffers`, `xlog_cil_push_work`, `xfs_trans_run_precommits` 以及 `xfs_extent_busy_sort` 在 ex4 及 btrfs 的測試中可以了解到 Timsort 對於 ex4 的表現並不會比原先的 list_sort 較為優異,但在 btrfs 則會有更好的結果。 > 過去在做其他實驗測量程式執行時間時,常會受到環境中其他因素影響,是否有可以像是 dudect 類似的方法可以更公正的測量程式碼執行時間? 而在 perf stat -M 的實驗測試中雖然 Timsort 比較次數較多,但執行時間則較少。 > 是否可能就是上述的所說環境中其他因素影響? 而最後也用同樣方法測量 xfs 檔案系統,在部份的結果中 Timsort 也都取得優異的表現。 > 思考可以改善的方式,是否可以讓 Timsort 在第一次掃描全部資料時若分佈不如預期,就使用原先的 list_sort 來排序? ## [位元運算的應用](https://hackmd.io/@sysprog/rkt-cdRrC) 目標 : 解析[第七週測驗題](https://hackmd.io/@sysprog/linux2024-quiz7) 測驗 1, 2 及[第九週測驗題](https://hackmd.io/@sysprog/linux2024-quiz9) 測驗 3 ### 第七週測驗題 測驗 1 :::success #### 延伸問題1. 解釋上述程式碼運作原理 ::: 首先先介紹如何判斷一個數是否為奇術,`bool odd(uint32_t x){return x & 1}`,為減少運算量,我們可以將兩個 32 位元合併為 64 位元並用特殊的位元遮罩做計算,此時可以寫成 ```c static inline uint64_t bit_compound(uint32_t x, uint32_t y) { return ((0L + x) << 32) | ((y + 0L) & (-1L >> 32)); } ``` 作者這邊提出 `& (-1L >> 32))` 應該是不必要的操作。 >在規格書中 6.1.3.8 Usual arithmetic conversions 有提到型別在位移時會自動轉換,因此我認為這邊應該也可以不需要額外的 0L 操作 >> Many operators that expect operands of arithmetic type cause conversions and yield result types in a similar way. The purpose is to determine a common real type for the operands and result. For the specified operands, each operand is converted, without change of type domain, to a type whose corresponding real type is the common real type. 接著查看 Linux 核心中的 [lib/string.c](https://github.com/torvalds/linux/blob/master/lib/string.c) 中 [memchr](https://man7.org/linux/man-pages/man3/memchr.3.html) 實作用來在記憶體區塊 str 中搜尋字元 c: ```c /** * memchr - Find a character in an area of memory. * @s: The memory area * @c: The byte to search for * @n: The size of the area. * * returns the address of the first occurrence of @c, or %NULL * if @c is not found */ void *memchr(const void *s, int c, size_t n) { const unsigned char *p = s; while (n-- != 0) { if ((unsigned char)c == *p++) { return (void *)(p - 1); } } return NULL; } ``` 而透過 [SIMD within a register (SWAR)](https://en.wikipedia.org/wiki/SWAR) 的技巧,我們可改寫為以下 memchr_opt 函式: ```c #include <limits.h> #include <stddef.h> #include <stdint.h> #include <string.h> /* Nonzero if X is not aligned on a "long" boundary */ #define UNALIGNED(X) ((long) X & (sizeof(long) - 1)) /* How many bytes are loaded each iteration of the word copy loop */ #define LBLOCKSIZE (sizeof(long)) /* Threshhold for punting to the bytewise iterator */ #define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) #if LONG_MAX == 2147483647L #define DETECT_NULL(X) (((X) - (0x01010101)) & ~(X) & (0x80808080)) #else #if LONG_MAX == 9223372036854775807L /* Nonzero if X (a long int) contains a NULL byte. */ #define DETECT_NULL(X) \ (((X) - (0x0101010101010101)) & ~(X) & (0x8080808080808080)) #else #error long int is not a 32bit or 64bit type. #endif #endif /* @return nonzero if (long)X contains the byte used to fill MASK. */ #define DETECT_CHAR(X, mask) DETECT_NULL(AAAA) void *memchr_opt(const void *str, int c, size_t len) { const unsigned char *src = (const unsigned char *) str; unsigned char d = c; while (UNALIGNED(src)) { if (!len--) return NULL; if (*src == d) return (void *) src; src++; } if (!TOO_SMALL(len)) { /* If we get this far, len is large and src is word-aligned. */ /* The fast code reads the source one word at a time and only performs * a bytewise search on word-sized segments if they contain the search * character. This is detected by XORing the word-sized segment with a * word-sized block of the search character, and then checking for the * presence of NULL in the result. */ unsigned long *asrc = (unsigned long *) src; unsigned long mask = d << 8 | d; mask |= mask << 16; for (unsigned int i = 32; i < LBLOCKSIZE * 8; i <<= 1) mask |= mask << i; while (len >= LBLOCKSIZE) { if (DETECT_CHAR(CCCC, mask)) break; asrc++; len -= LBLOCKSIZE; } /* If there are fewer than LBLOCKSIZE characters left, then we resort to * the bytewise loop. */ src = (unsigned char *) asrc; } while (len--) { if (*src == d) return (void *) src; src++; } return NULL; } ``` 接著照作者的寫法逐步分析程式碼的原理 首先第一個迴圈使用到 UNALIGNED(src) 來確定是否有對齊記憶體 ```c /* Nonzero if X is not aligned on a "long" boundary */ #define UNALIGNED(X) ((long) X & (sizeof(long) - 1)) ``` > 這部份不太能理解作者的解釋,因此又自己查閱其原理。 > long 所佔據的大小為 8 因此這行表示只要 x 和 7 (i.e., 0b0111) 去做 AND 運算便可確定是否未對齊。以 x = 0x1002 為例,0x1002 & 0x7 == 2 未對齊,相當於確認 x 是否為 8 的倍數(i.e., 第三位底下是否都是 0) 進到迴圈後判斷 - 第一個判別式用以判斷 len 是否為 0,若為 0 則表所有字元已經掃描過一輪,而隨著每次檢查會將其值遞減,直至迴圈條件不成立。 - 第二個判別式用以判斷當前 src 所指向字元與欲搜尋之字元 d 是否匹配。 - 最後將 src 改指向下一個字元所在位址,直到位址值符合對齊條件。 接著進到第二個條件判斷,首先判斷 `if (!TOO_SMALL(len))` 當輸入字串長度 ≥ 8 時,判別式條件成立,並強制轉型 `unsigned long *asrc = (unsigned long *) src;`。 接著透過 `or` 及左移運算,使變數 mask 為欲搜尋字元 d 的重覆擴展(擴展次數會因不同機器上的 `long` 型別位元數而有所不同)。 ```c unsigned long mask = d << 8 | d; mask |= mask << 16; for (unsigned int i = 32; i < LBLOCKSIZE * 8; i <<= 1) mask |= mask << i; ``` 接著使用 DETECT_CHAR 及 DETECT_NULL 來檢測 c 是否出現在字串中 ```c #define DETECT_CHAR(X, mask) DETECT_NULL((X) ^ (mask)) ``` 由於 mask 是由 c 重複擴展的 mask 因此可以先用 `^` 來確認是否有出現 c 這個字元,相同的部份則設成 0x00 再由 DETECT_NULL 來確認是否有 0x00 的存在。 接著查看 DETECT_NULL 的使用 ```c= #if LONG_MAX == 2147483647L #define DETECT_NULL(X) (((X) - (0x01010101)) & ~(X) & (0x80808080)) #else #if LONG_MAX == 9223372036854775807L /* Nonzero if X (a long int) contains a NULL byte. */ #define DETECT_NULL(X) \ (((X) - (0x0101010101010101)) & ~(X) & (0x8080808080808080)) #else #error long int is not a 32bit or 64bit type. #endif #endif ``` 若系統為 32 位元則使用第二行來偵測是否有出現 NULL,若 64 位元則使用第七行,若接非則用第 9 行跳出。 以 32 位元為例,首先 x 與 0x01010101 相減,若其中某個段落為 0x00 則會變為 0xFF。接著再和 x 的取反便可留下 0x00 出現的位置 (其他不是 0x00 的部份和自己的 not 做運算會變成 0x00), 最後再和 0x80808080 做 AND 便可識別出出現 NULL 的位置。 :::success #### 延伸問題2. 比較 Linux 核心原本 (與平台無關) 的實作和 memchr_opt,設計實驗來觀察隨著字串長度和特定 pattern 變化的效能影響 ::: 這部份作者僅討論要如何選定資料長度來當作樣本,並列舉出實驗所需要的數據大小,並未進行實驗,因此是可以做改進的地方。 :::success #### 延伸問題 3. 研讀 [2022 年學生報告](https://hackmd.io/@arthur-chang/linux2022-quiz8),在 Linux 核心原始程式碼找出 x86_64 或 arm64 對應的最佳化實作,跟上述程式碼比較,並嘗試舉出其中的策略和分析 > 下一步:貢獻程式碼到 Linux 核心 > [Phoronix 報導](https://www.phoronix.com/news/Linux-Kernel-Faster-memchr) ::: 同樣這個延伸問題並未完成,但也讓我好奇 linux 對於不同的硬體設備是否皆會做相對應的優化,又或者 linux 核心如何適應各種不同的硬體設備的? ### 第七週測驗題 測驗 2 ### 第九週測驗題 測驗 3 ## [虛擬攝影機裝置](https://hackmd.io/@sysprog/HJBxRsRr0) ## [重做 lab0](https://hackmd.io/@sysprog/Syc7ROemA) ## [CPU 排程器](https://hackmd.io/@sysprog/BJdyxvxX0) ## [vcam 研究](https://hackmd.io/@sysprog/ByzkF6xDA) ## [虛擬攝影機裝置驅動程式](https://hackmd.io/@sysprog/S1l3ZlcLA)