2016q3 Homework1 (phonebook)
===
contributed by <`kaizsv`>
>>請依照格式填寫標題和"contributed by"[name=課程助教]
## 開發環境
- lubuntu 16.04
- CPU: Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
- Memory: 8 GB
- Cache:
- L1d: 32K
- L1i: 32K
- L2: 256K
- L3: 3072K
## phonebook
## phonebook_orig
`$ perf stat -r 100 -e cache-misses,cache-references,instructions,cycles ./phonebook_orig`
```
Performance counter stats for './phonebook_orig' (100 runs):
3,421,394 cache-misses # 93.107 % of all cache refs
3,674,686 cache-references
261,157,949 instructions # 1.46 insns per cycle
179,200,521 cycles
0.068406278 seconds time elapsed
```
## phonebook_opt
#### 1. 改寫 structure __PHONE_BOOK_ENTRY
參考[programming small](https://hackmd.io/s/S1rbwmZ6#案例分析-phone-book),append() 及 findName()只會處理 lastName 欄位,而原本的 entry structure 包含了其它欄位,在這個例子裡我們可以用另外一個 structure 存放原本 entry 中除了 lastName 外的其它欄位,透過降底 entry size 減少 cache-miss 次數。
```
Performance counter stats for './phonebook_opt' (100 runs):
1,198,333 cache-misses # 74.482 % of all cache refs
1,608,887 cache-references
244,631,790 instructions # 2.00 insns per cycle
122,607,619 cycles
0.047359455 seconds time elapsed
```
很明顯 cache-misses cache-references 減少約一半,cycles 也減少且 IPC 提高到 2。
#### 2. smaz 壓縮
使用老師提供的連結 [smaz](https://github.com/antirez/smaz),但是壓縮字串須要給壓縮前的長度及壓縮後的長度,解壓縮回來也須要知道壓縮前的字串長度,不然不會準確以`\0`為字串結尾。如此就不能在 `findName()` 直接以 `strcasecmp()` 比較字串。
因此我的做法是直接在 `main.c` 壓縮字串,而不直接改 `phonebook_opt.c`,而且沒有解壓縮,直接使用壓縮後的字串進行比對,等於是把壓縮演算法想成 hash function。我手動設壓縮率為50%,且 entry 的 lastName 可以為 8 bytes 就好。
`phonebook_opt.h`
```=
typedef struct __PHONE_BOOK_ENTRY {
char lastName[8];
detail *pDetail;
struct __PHONE_BOOK_ENTRY *pNext;
}
```
`main.c`
```=
while (fgets(line, sizeof(line), fp)) {
while (line[i] != '\0')
i++;
line[i - 1] = '\0';
#if defined(OPT)
int smaz_size = (float) (i-1) * SMAZ_LAST_NAME_SCALE + 0.5f;
char smaz_line[smaz_size];
smaz_compress(line, i - 1, smaz_line, smaz_size);
#else
char smaz_line[MAX_LAST_NAME_SIZE];
strcpy(smaz_line, line);
#endif
i = 0;
e = append(smaz_line, e);
}
```
要搜尋再把壓縮 input 壓縮後傳到`findName()`。
```
Performance counter stats for './phonebook_opt' (100 runs):
832,114 cache-misses # 73.942 % of all cache refs
1,125,354 cache-references
742,530,000 instructions # 1.52 insns per cycle
487,882,795 cycles
0.184548656 seconds time elapsed
```
![](https://i.imgur.com/1PW98Z6.png)
從 perf 可以看到 cache-misses 又下降一些,`append()`時間變成3倍,`findName()`時間變為 1/3 倍。但`append()`時間還是太長了,我把程式碼放在 [smaz branch](https://github.com/kaizsv/phonebook-1/tree/smaz)。
#### 3. pthread
[去年](https://embedded2015.hackpad.com/Homework-2-RB9dqLyHq5h)有做了hash function, red-black tree, [fuzzy search](https://embedded2015.hackpad.com/HW-5-rnnoTmsgZqr)等版本,因此就直接做 pthread。
先研究 [這份資料](http://stackoverflow.com/questions/12282393/how-to-synchronize-manager-worker-pthreads-without-a-join),他希望不要用`pthread_join()`就可以讓 main thread 不做額外的 signal 並等待其它 thread 結束,[`pthread_cond_wait()`](https://linux.die.net/man/3/pthread_cond_wait)要與 mutex lock 一起呼叫,它會釋放 mutex lock 且 block 在 pthread_cond_t,允許其它人取得 mutex lock,因此我參考他的做法,在`pthread_creat()`後用迴圈等待其它 thread 完成。
```=
while (not_read_end) {
pthread_mutex_lock(&finish_mutex);
while (finished < NUM_OF_THREADS) {
pthread_cond_wait(&finish_cond, &finish_mutex);
}
pthread_mutex_unlock(&finish_mutex);
}
```
在 thread function 裡有判斷結束的條件,也就是 `fgets(line, sizeof(line), fp) == NULL`,此時`main thread`因`finish_cond`而等待,`finish_mutex`被釋放,就可以讓全域變數加1,當所有thread 都加1後就會被`main thread`cancel。
參考資料的做法是要等所有的 thread 都準備好的時候再一起執行,我想說作業要求分析`append()`的時間,就沒有做這部份,只確保所有 thread 執行完後再回到 main thread。
```=
while (1) {
pthread_mutex_lock(¤t_mutex);
not_read_end = fgets(line, sizeof(line), fp);
pthread_mutex_unlock(¤t_mutex);
if (not_read_end == NULL)
break;
while (line[i] != '\0')
i++;
line[i - 1] = '\0';
i = 0;
data->e = append(line, data->e);
}
pthread_mutex_lock(&finish_mutex);
finished++;
pthread_cond_signal(&finish_cond);
pthread_mutex_unlock(&finish_mutex);
pthread_exit(NULL);
```
下面分別是 2 個 thread 及 5 個 thread 版本的效能分析。
```
2 threads
Performance counter stats for './phonebook_opt' (100 runs):
1,003,743 cache-misses # 11.291 % of all cache refs
8,889,500 cache-references
439,770,486 instructions # 0.76 insns per cycle
576,668,041 cycles
0.126104883 seconds time elapsed
```
```
5 threads
Performance counter stats for './phonebook_opt' (100 runs):
949,669 cache-misses # 6.498 % of all cache refs
14,614,192 cache-references
500,208,293 instructions # 0.57 insns per cycle
873,157,426 cycles
0.135140143 seconds time elapsed
```
![](https://i.imgur.com/ow0zPrj.png)
因為沒有修改`phonebook_opt.c`我猜想`findName()`時間會下降,是因為`pHead->lastName`的順序已經不是照字母排了,剛好`zyxel`在比較前面的位置,不過這只是我的猜想,沒有證明。
## BUG
有時候`make plot`會發生`findName() assert`錯誤的情況,找不到輸入字串,有先試著用 GDB 看看是什麼情況,還是沒有解決,稍晚再試。
###### tags: `assigment_1` `phonebook`