# 2017q3 Homework2 (prefix-search)
contributed by <`HMKRL`>
### Reviewed by `jcyztw`
* 最近一次 [commit 2d47e0b](https://github.com/HMKRL/prefix-search/commit/2d47e0b35c4ff1392292709502f974101eba6c09),如同老師在你 GitHub 的留言,有空可以針對 commit message 的內文做修改。看完你的 commit message,我只看到你改了哪些檔案,但是你此次 commit 做了什麼事並沒有寫出來;因為我還沒仔細看你的 code,無法很迅速得知你這次 commit 到底做了什麼,覺得有點美中不足。(P.S. [How to Write a Git Commit Message 中文翻譯在這裡](https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/),供你參考)
* 請問你的開發環境為何?是單核心或是多核心的 CPU?我會問這個問題,主要是因為看到你在做 `test_cpy` 和 `test_ref` 的效能分析時,用了 `RDTSC` 指令來幫你達成目的。但是根據以前修課同學的[共筆](https://hackmd.io/s/BJBZn6Q6)及其[相關影片](https://youtu.be/DRkXFjLfaVg),`RDTSC` 指令在多核心 CPU 的環境上作量測會有問題。所以你才會使用 `chrt` 和 `taskset` 兩個 command 來解決 `RDTSC` 不能在多核心 CPU 量測的限制嗎?
### Reviewed by `amikai`
- 建議先提及理論, 在對應到自己的 code, 報告會更有完整性
- 我找到一個函式 sched_setaffinity(), 可以試試看
- tst_traverse_fn 這邊建議可以在介紹的詳細一點,因為這是很有用的函式, 並且可以舉個
實例
<s>以上是小弟淺見, 如有冒犯請見諒</s>
:::danger
1. 凡事都要怕冒犯,就不要去做了
2. 只要能提升其他同學程度的事,就大膽作
3. 指出他人的弱點,大家才能一同成長
:notes: jserv
:::
## 實作 `REF` 模式
根據程式碼註解:
```c
if (cpy) { /* allocate storage for 's' */
const char *eqdata = strdup(*s);
if (!eqdata)
return NULL;
curr->eqkid = (tst_node *) eqdata;
return (void *) eqdata;
} else { /* save pointer to 's' (allocated elsewhere) */
curr->eqkid = (tst_node *) *s;
return (void *) *s;
}
```
`REF` 模式應該將字串另外儲存,理想情況應該直接採用原始資料儲存的位置,但在這個例子中原始資料是讀檔取得,因此無法直接使用
:::danger
1. 可考慮引入 mmap,詳見 [B07: phonebook-concurrent](https://hackmd.io/s/rkOExTOoe)
2. 承上,引入 mmap 時,需要一併將 Stream I/O (如 `fopen`, `fread`, `fclose`) 替換為低階 I/O (如 `open`, `read`, `close`)
3. 不要忽略檔案操作在整體時間的影響
:notes: jserv
:::
針對這個問題和 [HTYISABUG](https://github.com/HTYISABUG) 討論,我們想到可以將原始資料儲存起來,但若同樣採用 `malloc` 個別分配記憶體空間,則與 `CPY` 版本沒有差異,因此我們決定採用 mempool 儲存原始資料
詳見 [commit d3eb39c](https://github.com/HMKRL/prefix-search/commit/d3eb39c93f66c5a128c5a3e0506017607d60da6f)
實作 mempool 後發現無法正常完成 delete 操作,經 gdb 檢查找到問題在 `tst.c` 中:
```c
if (del) { /* delete instead of insert */
(curr->refcnt)--; /* decrement reference count */
/* chk refcnt, del 's', return NULL on successful del */
- return tst_del_word(root, curr, &stk, 1);
+ return tst_del_word(root, curr, &stk, cpy);
}
```
這裡直接使用1傳入造成 delete word 時錯誤對 mempool 進行 free , 修改為傳入正確的模式變數 `cpy` 即可正常運作
## 效能測試
首先,為兩個不同的 test 檔加入讀取 `--bench` 選項的部份:
```
/* process --bench flag */
int bench_flag = 0;
struct option long_opt[] = {
{"bench", no_argument, &bench_flag, 1},
{0, 0, 0, 0}
};
getopt_long(argc, argv, "", long_opt, NULL);
```
並加入以下計時用程式碼,嘗試計算 `tst_search_prefix` 所需時間
```
for (int i = 0; i < SEARCH_TIMES; ++i) {
fscanf(infp, "%3s", prefix);
t1 = tvgetf();
tst_search_prefix(root, word, sgl, &sidx, LMAX);
t2 = tvgetf();
fprintf(outfp, "%d %.6lf\n", i, t2 - t1);
}
```
但檢視輸出檔後發現由於搜尋所花時間太短,無法有效測量
>> 為何不透過統計模型來分析呢?
>> [name="jserv"][color=red]
>> 此處消耗時間已低於本計時函式精確度,因此數據無法使用
>> [name="HMKRL]
因此引入作業1中曾出現過的方法,利用 [RDTSC](http://x86.renejeschke.de/html/file_module_x86_id_278.html) 計算所消耗的 cpu cycle
```
for (int i = 0; i < SEARCH_TIMES; ++i) {
fscanf(infp, "%3s", prefix);
get_cycles(&timec_high1, &timec_low1);
tst_search_prefix(root, word, sgl, &sidx, LMAX);
get_cycles_end(&timec_high2, &timec_low2);
timec = diff_in_cycles(timec_high1, timec_low1, timec_high2, timec_low2);
fprintf(outfp, "%d %ld\n", i, timec);
}
```
> 你輸入 search 的 prefix 從頭到尾沒變啊
> [name=HTYISABUG][color=black]
結果:
![](https://i.imgur.com/Bp2FfOl.png)
重新設定範圍消除過高資料後(來源應為作業系統其他背景工作):
![](https://i.imgur.com/BeVFElw.png)
>> 搭配使用 [chrt](http://man7.org/linux/man-pages/man1/chrt.1.html),將排程策略變更為 real-time FIFO,以及用 [taskset](https://linux.die.net/man/1/taskset) 限定在某個/某些處理器上運作程式
>> [name="jserv"][color=red]
>>
使用 `sudo chrt -f 99 taskset -c 0 ./test_cpy --bench` 進行測試
並修改 gnuplot script 改用長條顯示使結果更明顯,得到以下結果:
![](https://i.imgur.com/9rQZbeh.png)
計算標準差及95%信賴區間
```
CPY:
AVG = 1032.330220
SIGMA = 2273.788154
95%: 1023.575096 - 1041.085344
```
```
REF:
AVG = 998.183646
SIGMA = 1967.461852
95%: 990.608019 - 1005.759274
```
![](https://i.imgur.com/EwXYKYB.png)
計算標準差及95%信賴區間
```
CPY:
AVG = 34.966300
SIGMA = 2.620935
95%: 34.914930 - 35.017670
```
```
REF:
AVG = 32.950000
SIGMA = 3.629959
95%: 32.878853 - 33.021147
```
其中 `CPY` 模式的 cache-miss 為 24.305%
`REF` 模式則為 19.859%
可見採用連續記憶體空間可以有效降低 cache-miss
:::danger
從隨機事件的觀點,用 poisson distribution 解釋 REF 和 CPY 實作
:notes: jserv
:::
## 觀察 tst.h 和 tst.c
此處對 `tst_node` 進行 forward declaration:
```
/* forward declaration of ternary search tree */
typedef struct tst_node tst_node;
```
這樣做可以帶來的優點:
1. 通過 `typedef` 讓後續程式碼可讀性增加(省略 `struct`)
2. 若修改 `tst_node` 中的實作時,其他使用到 `tst.h` 的原始碼檔案不需要重新編譯,當專案規模增加時能節省許多編譯時間
但使用上有條件限制:若程式碼中需要得知 `sizeof(tst_node)` 即無法使用(例如用到 `malloc` 或將 `tst_node` 用於其他 struct 中)
在 phonebook 中,也能以同樣的方法將 entry 做 forward declaration ,在 opt 和 origin 中在詳細定義,即可共用 `phonebook.c`
## 觀察 tst_traverse_fn
本 function 提供了一個 debug 介面,使用者可以使用自己的函式作為 function pointer 傳入,即可用於除錯/觀察資料結構/效能量測等用途