# 2024q1 Homework5 (assessment) contributed by < [jeremy90307](https://github.com/jeremy90307) > :::danger 使用指定格式書寫,注意細節! ::: ## 閱讀 [因為自動飲料機而延畢的那一年](https://blog.opasschang.com/the-story-of-auto-beverage-machine-1/) :::danger 留意書名號的使用。 ::: 當我看到最後時讓我非常感動,在看這之前還想說 Linux 核心跟這到底有什麼關係,原本只是為了寫心得而去看,卻越看越認真的看完了,故事內容沒有什麼華麗的詞彙或有趣的劇情,用單純認真的態度去紀錄自己的故事,到故事的結尾那段 **我要選擇全世界第三杯的紅茶 !** 真的是打動到我,我的人生從未為了什麼目標放手一搏,我在想或許是我太過膽小害怕於改變。 在課程的後續我更應該要為自己負責,每個作業都很多可以學習的東西,若是中間有問題要做的不應該是拖延,而是當下解決問題,對每個實驗都應該更全面去思考問題,訓練自己對細節的重視! ## 檢視前六周學習狀況 ### 作業回顧 [M01: lab0](https://hackmd.io/@sysprog/linux2024-lab0/%2F%40sysprog%2Flinux2024-lab0-a) > [共筆](https://hackmd.io/GU68poa6RBqvgaM5PZ6UlA) 初次認識 Linux 核心 API 相關實作,當初還算認真的作完 queue 部分,後續補上 dudect 的論文研讀及實作及 list_sort 實作及 shuffle 命令等,過程中參考過很多人的共筆,當在看看自己打的程式碼時總覺得自己是一坨大便,因此在 lab0 還需待改進。 [M02: quiz1+2](https://hackmd.io/@sysprog/BkmmEQCn6) > [共筆](https://hackmd.io/GQ3oKwkZTQuPu47UR46PRQ) 這份作業現在看來完整度非常差,尤其在 Timsort 部分,當初沒搞懂Timsort 卻被我的拖延症拖到現在,目前就決定先改進它。 [M03: review](https://hackmd.io/@sysprog/linux2024-review) 將 `jimmy01240397` 的 code review 意見加入到我的專案中。 [M03: ttt](https://hackmd.io/@sysprog/linux2024-ttt) > [共筆](https://hackmd.io/GU68poa6RBqvgaM5PZ6UlA) 目前僅將 `ttt` 原封不動的加入到 `lab0` 中,且能順利運行井字遊戲,後續定點數相關實作待完成。 [M04: quiz3+4](https://hackmd.io/@sysprog/HkatSCZCT) > [共筆](https://hackmd.io/iFUiIM-BQKCgxtWdOi0Tfg) 這份作業讓我初次真正認識到 bitwise 的有趣之處,像是僅依靠位移操作就能達到幾乎等效除十的答案,做著做著幾天就過去了,雖然延伸問題還未完全做到。 XTree 中還是有些許不懂,但卻讓我更認識 rb tree、AVL Tree、B tree 的概念,像是其中透過左旋、右旋來平衡樹的方法。 ### homework5 預期目標 由於對自身評估,將全部作業完整將花費到大量時間,因此先將以下兩個作業完成,後續有多餘時間在繼續精進。 1. 將 quiz1+2 中的 Timsort 部份完整 2. 完成 ttt 作業要求,至少要作到參照〈並行程式設計:排程器原理〉,引入若干 coroutine > [lab0-ttt](https://hackmd.io/GU68poa6RBqvgaM5PZ6UlA?view#Tic-Tac-Toe) ## 檢視想投入之專案 > [2023 年期末專題](https://hackmd.io/@sysprog/linux2023-projects) :::danger 依據你的付出,還沒辦法看出「實力」 ::: 根據我現在<s>實力</s> ,我認為還沒法對專案做出什麼實質貢獻,但我希望可以從做專案的過程學習到 Linux 相關工作技能,希望以後可以進入到這個行業中,因此我希望老師能給我個方向在之後的期末專案,謝謝。 TODO: 〈並行程式設計:排程器原理〉 紀錄問題 一對一討論: **Timsort 在 Merge sort 的基礎之上,做了什麼改進?** 回顧 `測驗 2` Timsort 結合 Merge sort 和 Insertion Sort,改進的目的有以下: 1. 加速 merge 的過程 2. 減少合併次數 3. 在處理有部分已排序的資料下,有更好的表現 值得一提的是, Timsort 將資料由 run 所構成,這裡的 run 由部分已排列的資料所構成。 那 run 的長度該怎麼決定,定義一個 minrun (最小運行長度),用以表示每個 run 的最小長度。有幾點需要我們考慮到: - minrun 不宜太長,會造成花費更多時間在執行插入排序 - minrun 不宜太短,否則在進行 merge 時次數變多 - 理想情況下 run 的數量等於或者略小於 2 的冪,效率會最高 在 `find_run` 函式會尋找符合遞增順序的元素,並組成一個 run ,接著使用 `merge_collapse` 來讓 run 的數量保持平衡,且須滿足以下條件: - 假設有三個 run 分別為 A、B、C - A > B + C - B > C 只要有一項不符合則將 B 與 A、C 中較小者進行合併。 :::info 合併分成兩種: - 在這我們假設有 run A 及 run B ``` A : [3, 7, 11, 12, 13, 31, 45] B : [21, 22, 24, 24, 29, 300, 400] ``` 1. merge sort 從 A、 B 的開頭逐一比較來進行合併,稱為 `one-pair-at-a-time` 2. Timsort 的合併基於 merge sort 做出改進,首先將 A 與 B 中長度較短者放入一塊臨時記憶區 `temp` (這裡 A 為較短者) ``` A : [] B : [21, 22, 24, 24, 29, 300, 400] temp : [3, 7, 11, 12, 13, 31, 45] ``` 接著 B 的第一個元素 $B_0$ 會去找在 A 的何處 ($A_4$ 與 $A_5$ 之間),並將 $A_0$ ~ $A_4$ 放回 A 數組中 ``` A : [3, 7, 11, 12, 13] B : [21, 22, 24, 24, 29, 300, 400] temp : [31, 45] ``` 接著將 temp[0] 比對 B ,介於 B[4] 與 B[5] 之間 ``` A : [3, 7, 11, 12, 13, 21, 22, 24, 24, 29] B : [300, 400] temp : [31, 45] ``` 以此類推 ``` A : [3, 7, 11, 12, 13, 21, 22, 24, 24, 29, 31, 45] B : [300, 400] temp : [] ``` ==> ``` A : [3, 7, 11, 12, 13, 21, 22, 24, 24, 29, 31, 45, 300, 400] B : [] temp : [] ``` 這種合併方法被稱為 Galloping mode 。 ::: 優劣: 數組若為隨機資料序列,可能會比逐對合併來的更慢。 Timesort 基於 mergesort 做了那些改進? - 改善比較次數 Timsort 會將已排序的子序列組成 run 並透過一些規則使 run 的數量保持平衡,從而讓合併時使用 Galloping mode 來減少比較次數。 - 改善記憶體開銷 相較 merge sort ,我們僅需將以排序的兩個 run 重疊部分進行合併。 舉例 [2, 4, 6, 7, 8, 11] 與 [9, 10, 11, 15, 17],僅須對重疊部分合併(即 [8, 11] 與 [9, 10, 11]),在配置額外空間,僅需配置合併部分及數組數量較小的那一方,因此只須配置 2 個空間即可。 :::info 這裡配置的空間為 [2, 4, 6, 7] 及 [8, 9, 10, 11, 11, 15, 17] 最後僅需將兩數組街上即可完成排序 => [2, 4, 6, 7, 8, 9, 10, 11, 11, 15, 17] ::: - 降低 cache miss Timsort 不必走訪每個序列才進行合併,而是一邊走訪,就一邊把序列的元素加進待合併的 runs 當中,在適當時機就合併。對記憶體來說這些元素因近期存過,所以還在記憶體的上層中,能有效減少 cache miss。 :::warning 看`測驗 2` 過程中 >在連續記憶體的情境,其合併的順序相對於非遞迴版對 cache 較友善,但對鏈結串列一類非連續性記憶體而言,進行分治(divide) 的過程中,意即決定要切在哪一個點決定左右子樹,要走訪目前遞迴呼叫對應到合併樹中內部節點,所有待排序的元素,對 cache 不友善。 這段我無法理解為何是什麼原因導致的對 cache 不友善。 ::: **如何用 bitwise 運算做到 integer div/mod 10?** > show me the code! ```c /* a / b or a % b, b = 10 */ void divmod10(int a, int *div, int *mod) { int q,r; q = ((a >> 3) + (a >> 1) + a) << 3) >> 7; r = a - (((q << 2) + q) << 1); *div = q; *mod = r; } ``` a = 23 10111 q = 2 r = 3 **數學證明** (a >> 3) 等於 $\dfrac{a}{8}$ (a >> 1) 等於 $\dfrac{a}{2}$ 最後再將結果加上 a 得到 ```c q_approx = (a / 8) + (a / 2) + a = a * (1/8 + 1/2 + 1) = a * 1.625 ``` 然後這個值會左移 3 位(乘以 8)再右移 7 位(除以 128): ```c q = ((a * 1.625) * 8) / 128 = (a * 13) / 128 = a * 0.1015625 ``` 根據餘數定理 $r = f-g\times Q$ ```c r = a - (((q << 2) + q) << 1); ``` 可以看作 ```c r = a - (10 * q); ``` 即可求出餘數 r **驗證** 假設 a = 591 近似商計算 ```c q_approx = ((591 >> 3) + (591 >> 1) + 591) = (73 + 295 + 591) = 959 q = (959 * 8) >> 7 = 59 ``` 近似餘數計算 ```c r = 591 - (((59 << 2) + 59) << 1) = 591 - 590 = 1 ```