# 2017q1 Homework4 (phonebook-concurrent)
contributed by <`petermouse`>
## 開發環境
* OS: Ubuntu 16.04 LTS
* Memory: 8 GB
* CPU:
* Name:
* Intel(R) Core(TM) i5-4200H CPU @ 2.80GHz
* Cache:
* L1d Cache: 32K
* L1i Cache: 32K
* L2 Cache: 256K
* L3 Cache: 3072K
## 執行程式
先執行原本的程式看看,由於 opt 版本多使用了 gprof ,避免時間影響先移除 `-pg`
![](https://i.imgur.com/bE6Rqw7.png)
`append()` 加快好多,快要接近 `findName()` 的時間了
## 分析程式碼
opt 比起 orig 做了幾個改動:
### 1. text align
將所有字以 16 bytes 對齊,固定的偏移量可以直接計算存取下一筆字的位置
### 2. `mmap()`
[mmap(2)](http://man7.org/linux/man-pages/man2/mmap.2.html)
![](http://docs.linuxtone.org/ebooks/C&CPP/c/images/io.mmap.png)
在 process 的 virtual address space 建立新的映射
[TLPI](http://man7.org/tlpi/) 一書提到 `mmap()` 可能帶來的優點
* 一般 `read()` 與 `write()` 要經過兩次的傳輸
file <--> kernel buffer cache <--> user space buffer [(參考圖片)](http://www.programering.com/a/MjN2EzNwATc.html)
不過使用 `mmap()` 省去 kernel buffer cache <--> user space buffer 的傳輸
* 等於 kernel 與 user 共享一個 buffer ,多個 process 也共用同個 buffer
### 3. Pthread
使用 Pthread 平行處理,預設為 4 個 thread
每個 thread 用 cyclic 的方式建立 local linked-list ,最後再串接起來
### 4. 其他
* 使用 Variadic Macros ([C](https://gcc.gnu.org/onlinedocs/gcc/Variadic-Macros.html) [C++](https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html)) 做出 debug 訊息
## 改進程式
### 建立 thread pool
#### 觀察 mbrossard 的 thread pool 程式碼
參考 [threadpool-mbrossard](https://github.com/mbrossard/threadpool) 的作法,先釐清一下內部的實作,不過覺得有點難懂
```clike
struct threadpool_t {
pthread_mutex_t lock;
pthread_cond_t notify;
pthread_t *threads;
threadpool_task_t *queue;
int thread_count;
int queue_size;
int head;
int tail;
int count;
int shutdown;
int started;
};
```
這是 thread pool 的資料結構,裡面是用 queue 來存放 task,thread 再從 queue 拿取 task
```clike
threadpool_t *threadpool_create(int thread_count, int queue_size, int flags)
```
建立一個 thread pool ,並指定 thread 數量以及 queue 大小
thread 會在此時就建立
```clike
/* in threadpool_create() function */
for(i = 0; i < thread_count; i++) {
if(pthread_create(&(pool->threads[i]), NULL,
threadpool_thread, (void*)pool) != 0){
/* error handling*/
}
}
```
不過每個 thread 執行的函式是 `threadpool_thread` ,參數是 `pool` ,也就是 `threadpool_thread` 是取得 task 的接口,並從參數 thread pool 中的 queue 接收 task
接下來的 thread 搶奪 lock 的過程原網頁都有提到了
```clike
int threadpool_destroy(threadpool_t *pool, int flags);
```
用來結束 thread pool , 分為 graceful shutdown ( `flags` 不為 0) 代表正常結束,以及 immediate shutdown ( `flags` 為 0),通知所有 thread 喚醒,並且離開 `threadpool_thread` 最後 join ,釋放空間
#### 在 phonebook-concurrent 內使用
在還沒使用 thread pool 之前,我預期結果會比原本還要慢,因為就只是把原本的 4 個 task (即 `thread_args[]` ) 先放入 queue 中, 4 個 thread 再去從 queue 拿取,比起直接 4 個 thread 分配各 1 個 task 多了初始化 thread pool 以及 強奪 lock 的成本
實際上是稍微久了一些,但不會差距太多
![](https://i.imgur.com/zjR58h9.png)
接著我試著把 task 切得更細,不過一直 core dumped,後來發現在給 thread 執行 `threadpool_thread` 內執行 task `append()` 時最後直接呼叫了 `pthread_exit(NULL)` ,竟然把 thread 直接結束了,剩餘的 task 並沒有執行到。
嘗試 `TASK=100` ,thread 維持在 4,結果奇怪的事情發生了
![](https://i.imgur.com/xMxZKIW.png)
`append()` 的時間變得更多了,我覺得算是正常的
不過 `findName()` 時間突然暴漲了,怎麼會這樣RRR?
由於是 cyclic 建立 linked list,所以 `zyxel` 也會隨著 task 的數量不同而放到 linked list 的不同位置,所以有機會可以秒殺找到
但是原始版本中是按照字典序建立 linked list ,找尋 `zyxel` 已經是接近最糟的情況,結果相差快兩倍了!
我也試著用 `show_entry()` 驗證正確性,這部份是沒問題的
我推測是頻繁的 cache miss但導致的狀況, 但尚未建立實驗
#### findName() 也用 thread pool
我把 `findName()` 也試著用thread pool 處理
同樣是 task=100 thread=4
![](https://i.imgur.com/r7BaJhN.png)
可以降低一些時間,但是會有幾個問題
* 原本的 `append()` 會 return 找到的 entry ,但現在 thread pool 的形式沒辦法有效取得 entry
* 解決辦法是在原本的 task 中紀錄結果,但最後還要從所有 task 找尋,很沒效率
* 一個 task 找到沒有辦法停止其他 thread 的找尋
* 我覺得有辦法再調整,還要在思考如何適當的停止 thread pool
#### mutrace 測試
參考 [B08: mergesort-conurrent](https://hackmd.io/s/B1xV_p_jl#)
使用 mutrace 測試 mutex 帶來的影響 (THREAD=4 TASK=100)
以下省略每個 mutex 的詳細資料
```
mutrace: 0.2 sucessfully initialized for process phonebook_tp (pid 2981).
orginal file size = 3206080
size of entry : 24 bytes
execution time of append() : 0.005685 sec
execution time of findName() : 0.007196 sec
mutrace: Showing statistics for process phonebook_tp (pid 2981).
mutrace: 3 mutexes used.
mutrace: Showing 3 most contended mutexes:
Mutex # Locked Changed Cont. tot.Time[ms] avg.Time[ms] max.Time[ms] Flags
1 206 106 36 0.029 0.000 0.000 Mx.--.
2 209 110 30 0.040 0.000 0.005 Mx.--.
0 40 24 10 0.005 0.000 0.001 M-.--.
||||||
/|||||
Object: M = Mutex, W = RWLock /||||
State: x = dead, ! = inconsistent /|||
Use: R = used in realtime thread /||
Mutex Type: r = RECURSIVE, e = ERRRORCHECK, a = ADAPTIVE /|
Mutex Protocol: i = INHERIT, p = PROTECT /
RWLock Kind: r = PREFER_READER, w = PREFER_WRITER, W = PREFER_WRITER_NONREC
mutrace: Note that the flags column R is only valid in --track-rt mode!
mutrace: Total runtime is 68.093 ms.
mutrace: Results for SMP with 4 processors.
```
`Locked`: mutex 鎖了幾次
`Changed`: thread 轉換的次數
`Cont.`: thread 嘗試要求已鎖住的 mutex 的次數
`tot.Time[ms]`: mutex 鎖住的總時間
`avg.Time[ms]`: 平均鎖住 mutex 的時間 (?)
`max.Time[ms]`: 持有 mutex 的最大時間
觀察 各個 mutex 的資訊
```
Mutex #1 (0x0x20da650) first referenced by:
/usr/lib/mutrace/libmutrace.so(pthread_mutex_init+0xf2) [0x7f206ff724b2]
./phonebook_tp(threadpool_create+0xf3) [0x4021ab]
./phonebook_tp(main+0x2df) [0x4017ab]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f206f9a9830]
```
輸入 `$ addr2line -e phonebook_tp 0x4021ab` 就得到 mutex 的位置
```
/home/petermouse/workspace/sysprog/phonebook-concurrent/threadpool.c:122
```
### 實作 remove
## 參考資料
[threadpool-mbrossard](https://github.com/mbrossard/threadpool)
[mutrace](http://0pointer.de/blog/projects/mutrace.html)