# 2017q1 Homework4 (phonebook-concurrent)
contributed by <`illusion030`>
---
## 開發環境
```
illusion030@illusion030-X550LD:~/Desktop/2017sysprog/phonebook-concurrent$ lscpu
Architecture: x86_64
CPU 作業模式: 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
每核心執行緒數:2
每通訊端核心數:2
Socket(s): 1
NUMA 節點: 1
供應商識別號: GenuineIntel
CPU 家族: 6
型號: 69
Model name: Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz
製程: 1
CPU MHz: 1649.475
CPU max MHz: 2600.0000
CPU min MHz: 800.0000
BogoMIPS: 4588.95
虛擬: VT-x
L1d 快取: 32K
L1i 快取: 32K
L2 快取: 256K
L3 快取: 3072K
NUMA node0 CPU(s): 0-3
```
---
## 開發過程
### 重現實驗
* fork [phonebook-concurrent](https://github.com/sysprog21/phonebook-concurrent) 後執行看看
![](https://i.imgur.com/nDNgyfo.png)
* append() 的時間甚至比 findName() 還低
### Refactoring
* 參考 [csielee 的共筆](https://hackmd.io/s/ByiOeFYie#) 及 [async.h](https://github.com/embedded2016/server-framework/blob/master/async.h) 和 [async.c](https://github.com/embedded2016/server-framework/blob/master/async.c)
* 將 phonebook 的 function 包成 API,使得 main.c 中只需要呼叫 API
* orig 的 API
```
struct __PHONEBOOK_API__ pb = {
.findLastName = orig_findName,
.append = orig_append,
.write = orig_write,
.free = orig_free,
};
```
* opt 的 API
```
struct __PHONEBOOK_API__ pb = {
.findLastName = opt_findLastName,
.append = opt_append,
.write = opt_write,
.free = opt_free,
};
```
* 實際跑看看,發現因為把 text_align() 放到 append() 裡執行,所以導致 append() 的時間大幅提昇
![](https://i.imgur.com/vQbstkh.png)
### Thread pool
* 參考 [concurrency 教材](https://hackmd.io/s/Skh_AaVix#thread-pool) 中的參考文件 [threadpool-mbrossard](https://github.com/mbrossard/threadpool) 的作法
* 將 phonebook 用 thread pool 的方式改寫
* Thread pool 實作原理
![](https://i.imgur.com/ysvbkbk.png)
* 建一個 queue 並且每個 thread 會等待 task 進入 queue,當我們把 task 放進 queue 中,threads 會去競爭這個 task,搶到 task 的 thread 就開始執行,執行完後回來等待 queue 的下一個 task。
* 當 task 很多時,可以達到平均 thread 執行時間的效果,因為當一個 thread 執行完不用等到其他 thread 結束,就可以繼續執行下一個 task 了
```=vim
pthread_mutex_t lock;
threadpool_t *pool;
pthread_mutex_init(&lock, NULL);
assert((pool = threadpool_create(THREAD_NUM, QUEUE_SIZE)) != NULL);
for (i = 0; i < TASK_NUM; i++)
threadpool_add(pool, (void *)&threads_append, (void *)thread_args[i]);
while(TASK_NUM != done) ;
assert(threadpool_destroy(pool, 0) == 0);
```
* 實際執行後發現不同的 QUEUE_SIZE 和 TASK_NUM 會影響效率,於是固定 QUEUE_SIZE 為 15000,TASK_NUM 從 1 跑到 9901 每次加 100,THREAD_NUM 為 4
![](https://i.imgur.com/pDDb7RQ.png)
* 發現執行時間非常的浮動,間距在 0.072556 到 0.144134 之間
```
max = 0.144134 in 5301
min = 0.072556 in 1301
```
* 執行時間最小 ( TASK_NUM = 1301 )
![](https://i.imgur.com/ORgpQwZ.png)
* 執行時間最大 ( TASK_NUM = 5301 )
![](https://i.imgur.com/eDzxalw.png)
* 認為是 TASK_NUM 放進 queue 跟 thread 拿出去之間關聯性影響了時間,要讓每個 thread 一直都有事做才會達到預期的平衡每個 thread 執行時間的效果
* 還要再思考看看 TASK_NUM 要怎麼取到最佳值
:::danger
要注意是否有 lock contention,可用 mutrace 追蹤 --jserv
:::