# 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
```