# 2017q1 Homework4 (mergesort-concurrent)
contributed by `<yangyang95>`
###### tags: `sysprog2017` `HW4` `yangyang95`
開發環境看 [這裡](https://hackmd.io/s/HJ2c_XYKx)
作業要求看 [這裡](https://hackmd.io/s/B1xV_p_jl#作業要求)
github page 看 [這裡](https://github.com/yangyang95/mergesort-concurrent)
## Doxygen 文件產生
這個作業超級複雜的阿 = =
不過現實開發中的程式一定會比這份作業更加複雜,所以必須趕快學習如何對複雜的專案進行開發
先學習 [0xff07 大大](https://hackmd.io/s/H154LN6og#mergesort-concurrent) 用 doxygen 產生文件幫助自己理解
參考了這篇文章 [Doxygen 程式文件產生器 與 簡易筆記](https://www.jerrynest.com/doxygen/) 安裝 doxygen 來使用
原本還想參考這篇 [Auto-deploying Doxygen documentation to gh-pages with Travis CI](https://gist.github.com/vidavidorra/548ffbcdae99d752da02)
利用 Travis-CI 自動部署 doxygen 文件的 github page,不過又失敗惹 orz
個人感覺 Travis 對 C 語言的專案不太友善啊...
只好用最簡單的方式手動製作 [Doxygen 文件](https://yangyang95.github.io/mergesort-concurrent)
## 程式碼修改
### 字串處理
來看看 link list 節點的資料結構
```C
typedef intptr_t val_t;
typedef struct node {
val_t data; /**< Data of the node */
struct node *next; /**< Pointer to the next node */
} node_t;
```
`intptr_t` 這個資料型別可以在不同位元上的機器調整大小
舉例來說,當機器是64bit
intptr_t 就是 long int ( 64位元的 long int 是64 bit, 但32位元的 long int 是32 bit)
否則就是 int ( 64位元的 int 是32 bit, 32位元的 int 也是32 bit)
直接把 `val_t` ,也就是 `intptr_t` ,拿來放字串的 ascii code
在 `main.c` 的讀取函式做了修改:
原本想使用
```C==
/* bilud_list_from_file func */
char buffer[16];
while (fgets(buffer, 16, fp) != NULL) {
list_add(_list, (val_t)buffer);
}
```
可是發現 list 出來都是亂碼,參考 [0xff07](https://github.com/0xff07/mergesort-concurrent/commit/c2de063fd09090e17caf5d68be1f1923dd9b0f1c) 做修改
```C==
/* bilud_list_from_file func */
char buffer[16];
while (fgets(buffer, 16, fp) != NULL) {
char *name = (char*)malloc(16);
strcpy(name, buffer);
list_add(_list, (val_t)name);
}
```
如此一來就可以正確讀取字串,我認為是 list_add 函式的傳入資料需為 pointer 導致亂碼
對 pointer 的掌握度實在還需要再加強
輸出 `list_print` 函式的部分也稍加修改
```C==
void list_print(const llist_t * const list)
{
const node_t *cur = list->head;
while (cur) {
xprint((char*)cur->data);
cur = cur->next;
}
}
```
如此一來就完成字串的支援囉,數字的排序依然可以使用
參考資料:
* [使用變數型別的良好習慣](http://b8807053.pixnet.net/blog/post/164224857-%E4%BD%BF%E7%94%A8%E8%AE%8A%E6%95%B8%E5%9E%8B%E5%88%A5%E7%9A%84%E8%89%AF%E5%A5%BD%E7%BF%92%E6%85%A3)
## 效能分析
既然能夠支援電話簿的輸入,那就來執行程式看看效能如何
```
$ ./sort 4 test_data/words.txt (sorted)
#Total_tasks_consumed: 12
#Elapsed_time: 99.519 ms
#Throughput: 120 (per sec)
```
```
$ ./sort 10 test_data/words.txt (sorted)
#Total_tasks_consumed: 30
#Elapsed_time: 121.599 ms
#Throughput: 246 (per sec)
```
使用 `$ make genData`
也就是 `uniq test_data/words.txt | sort -R > test_data/input.txt` 來產生所有名字只出現一次 (uniq) 且亂序排列 (sort -R) 的電話簿
:::warning
注意: 根據 man page,sort 是基於 shuf 工具來亂序排列,而老師上課時有提到過這個工具並非完全隨機。有機會可以驗證看看。
:::
```
$ ./sort 4 test_data/input.txt (random order)
#Total_tasks_consumed: 12
#Elapsed_time: 206.756 ms
#Throughput: 58 (per sec)
```
```
$ ./sort 10 test_data/input.txt (random order)
#Total_tasks_consumed: 30
#Elapsed_time: 247.542 ms
#Throughput: 121 (per sec)
```
又再一次將每一個實驗都再跑一次,發現 `Total_tasks_consumed` 不變,但 `Elapsed_time` 跟 `Throughput` 都有不小的變動。
```
$ ./sort 4 test_data/words.txt (sorted)
#Total_tasks_consumed: 12
#Elapsed_time: 69.094 ms
#Throughput: 173 (per sec)
```
```
$ ./sort 10 test_data/words.txt (sorted)
#Total_tasks_consumed: 30
#Elapsed_time: 82.656 ms
#Throughput: 362 (per sec)
```
```
$ ./sort 4 test_data/input.txt (random order)
#Total_tasks_consumed: 12
#Elapsed_time: 196.514 ms
#Throughput: 61 (per sec)
```
```
$ ./sort 10 test_data/input.txt (random order)
#Total_tasks_consumed: 30
#Elapsed_time: 311.850 ms
#Throughput: 96 (per sec)
```
重新執行一遍 `$ make genData` 來產生不同亂序的電話簿來比較
```
$ ./sort 4 test_data/input1.txt (random order)
#Total_tasks_consumed: 12
#Elapsed_time: 192.473 ms
#Throughput: 62 (per sec)
```
```
$ ./sort 10 test_data/input1.txt (random order)
#Total_tasks_consumed: 30
#Elapsed_time: 211.888 ms
#Throughput: 141 (per sec)
```
將上方實驗流程用 Flow Chart 做圖
```flow
op=>operation: 4,10 threads 排序過字典 (第一次)
op2=>operation: 4,10 threads 亂序字典 (第一次)
op3=>operation: 4,10 threads 排序過字典 (第二次)
op4=>operation: 4,10 threads 亂序字典 (第二次)
op5=>operation: 重新產生亂序字典
op6=>operation: 4,10 threads 亂序字典
op->op2->op3->op4->op5->op6
```
(這圖好大啊 XD)
接下來就實驗結果做圖來比較
**執行時間** :
![](https://i.imgur.com/NXIJuZt.png)
> 註:最後兩條 4,10 threads random (third) 與前面的 random 使用不同的亂序字典
由這張圖可以看出執行時間可能有以下趨勢:
$$ 4 執行緒(排序) < 10 執行緒(排序) < 4 執行緒(亂序) < 10 執行緒(亂序) $$
使用不同的亂序字典則有以下關係:
$$ 4 執行緒(字典1) \approx 4 執行緒(字典2),10 執行緒(字典1) > 10 執行緒(字典2) $$
:::warning
不過此實驗只執行兩次,雖然可以幫助我們發現可能的趨勢,卻需要設計強度更高的實驗來驗證
:::
**Throughput** :
![](https://i.imgur.com/ncdZfN8.png)
由圖可看出通量:
$$ 4執行緒(亂序) < 4執行緒(排序) \approx 10 執行緒(亂序) << 10 執行緒(排序) $$
使用不同的亂序字典則有以下關係:
:::warning
如上所述,實驗代表性還不夠,還需要加以驗證
:::
## 正確性驗證
實驗排序跑出來的結果我存在 txt 裡,然後使用 `diff` 指令來跟原始的字典做比較,結果都正確。
不過執行之前留下來的驗證程式 `$ make check` 的時候卻出現錯誤,不知是何處的問題
稍微看了一下,`$ make check` 是用數字來驗證,不過結果看起來沒什麼問題,待我看看程式碼是如何執行驗證。
來分析 `$ make check` 的程式碼
```c
# Default variables for auto testing
THREADS ?= 4
TEST_DATA_FILE ?= /tmp/test_number.txt
NUM_OF_DATA ?= 1024
SORTED_DATA_FILE ?= $(TEST_DATA_FILE).sorted
SORTED_RESULT ?= /tmp/sort_result.txt
ITERATIONS ?= 100
check: sort
# Generate testing data
@bash scripts/gen-random-numbers.sh $(NUM_OF_DATA) $(TEST_DATA_FILE)
# Sort the testing data first to generate ground truth
@sort -g $(TEST_DATA_FILE) > $(SORTED_DATA_FILE)
# Use sort implementation to sort the testing data, and ignore first
# 3 lines of output. Because we only want the sorting result.
@./sort $(THREADS) $(TEST_DATA_FILE) | tail -n +4 > $(SORTED_RESULT)
@bash scripts/compare.sh $(SORTED_DATA_FILE) $(SORTED_RESULT)
```
使用 `gen-random-numbers.sh` 在 `/tmp` 資料夾建立隨機數字的文件
再用 `sort -g` 進行排序,作為 baseline
這邊解釋一下 `tail -n +4` 的作用,就是將結果從第 4 行開始輸出,避開前三行多出來的結果
程式碼讀到這裡,打開 `sort_result.txt` 一看,不得了...
```
sorted_result.txt: test_number.txt.sorted:
1000 56
10142 120
10154 170
10160 184
10195 266
```
數字都被當成字串處理 (這不是廢話嗎...)
::: warning
目前還沒有好的想法解決數字排列錯誤問題,不過字串排列結果確定是正確的
:::
## 研究 [concurrent linked-list](https://github.com/jserv/concurrent-ll)
這份專案內有兩個 concurrent linked list 的實作,分別是 lock-free 版與 lock-based 版
兩個實作都包含以下功能:
* 在 list 內增加項目
* 在 list 內刪除項目
* 在 list 內尋找項目
lock-free 的實作使用 Harris 演算法
lock-based 的實作使用 hand-over-hand locking 的技巧
lock 可以想象成十字路口的紅綠燈
* lock-based 指的是當兩個 thread 要對同一個記憶體空間作用時,一個先執行,另外一個需等待前一個執行完成
* lock-free 可想像成高架橋,兩個 thread 都不需等待紅綠燈