# 2017q1 Homework1 (phonebook)
contributed by < `laochanlam` >
###### tags: `laochanlam`
### Reviewed by `yanglin`
- 實驗裡面提到預先 allocate 350000 筆 entry 的資料,這個數字是如何得來的?是從輸入資料的規模得到的嗎?那在現實狀況中,要如何決定 memory pool 的大小?
- [commit 75f65b8](https://github.com/laochanlam/phonebook/commit/75f65b8216c577465742db8d166f044d1e5a0b69), commit message 的長度超過一行72個字元,而且有些冗余的情報。
- 只有使用一種 hash function 做測試,是否能夠比較不同種 hash function 的 performance?
### Reviewed by `Cayonliow`
* 你所使用的 hash function 的參考資料是跟我一樣的, 裏頭的 DJBHash 會更快, 利用不同方式的乘法的復雜度來達到更快的速度, 可以看看[我的共筆](https://hackmd.io/s/r1i2_OhFx#%E5%84%AA%E5%8C%96%E7%89%88%E6%9C%AC)
* 沒有提到 如何選取跟制定 hash table 的大小, 如果太大或太小又會怎樣
## 開發環境
- Ubuntu 16.10 (64bit)
>請列出硬體相關資訊,如:cache, memory
>[name=課程助教][color=red]
>已補上,感謝提醒!
>[name=laochanlam][color=blue]
```
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 61
Model name: Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz
Stepping: 4
CPU MHz: 2400.878
CPU max MHz: 3000.0000
CPU min MHz: 500.0000
BogoMIPS: 4788.90
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 4096K
NUMA node0 CPU(s): 0-3
```
## 相關連結
- [2017 年春季作業說明](https://hackmd.io/s/Bk-1zqIte#)
- [2017q1 Homework1 (作業區)](https://hackmd.io/MYJjEYGZITgWgOwCMAmw4BYYwAxyQQKxwBmwGOkAHMDeEgGxA===?view)
- [B01: phonebook作業要求](https://hackmd.io/s/rJYD4UPKe#b01-phonebook)
- [phonebook Github連結 ( laochanlam ) ](https://github.com/laochanlam/phonebook)
- [作業解說 video](https://www.youtube.com/watch?v=ZICRLKf_bVw)
- [課程進度與開放資源 Wiki](http://wiki.csie.ncku.edu.tw/sysprog/schedule)
## Phonebook 開發紀錄
### Optimize 方案 1: 縮小 entry 的 size
理解完整個專案後,終於開始動工了!
先是依照老師的提示把 entry 的 size 縮小,把除lastName以外的資料都存到新的 struct 中,並在原來的 struct 中建立一個指向 detail 的 pointer 。
#### 原來的版本
```C
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
char firstName[16];
char email[16];
char phone[10];
char cell[10];
char addr1[16];
char addr2[16];
char city[16];
char state[2];
char zip[5];
struct __PHONE_BOOK_ENTRY *pNext;
} entry;
```
使用Perf分析cache-misses的情況,原來的大約是81%。
```
Performance counter stats for './phonebook_orig' (100 runs):
3,430,005 cache-misses # 81.729 % of all cache refs ( +- 0.03% )
4,196,826 cache-references ( +- 0.19% )
261,712,845 instructions # 1.34 insn per cycle ( +- 0.02% )
194,998,572 cycles ( +- 0.26% )
0.066929775 seconds time elapsed ( +- 0.31% )
```
#### 優化後的版本
```C
typedef struct __PHONE_BOOK_DETAIL {
char firstName[16];
char email[16];
char phone[10];
char cell[10];
char addr1[16];
char addr2[16];
char city[16];
char state[2];
char zip[5];
} detail;
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
struct __PHONE_BOOK_ENTRY *pNext;
struct __PHONE_BOOK_DETAIL *pDetail;
} entry;
```
cache-miss變成69%了!!
```
Performance counter stats for './phonebook_opt' (100 runs):
1,223,454 cache-misses # 69.446 % of all cache refs ( +- 0.25% )
1,761,727 cache-references ( +- 0.99% )
242,934,591 instructions # 1.74 insn per cycle ( +- 0.02% )
139,951,145 cycles ( +- 0.91% )
0.048685568 seconds time elapsed ( +- 1.07% )
```
附個小圖:
![Optimize1](http://i.imgur.com/vYzt8hq.png)
### Optimize 方案 2: 利用 Hash function 儲存資料
接下來要進行 Hash 的優化,在參考完[各种字符串Hash函数比较](https://www.byvoid.com/zhs/blog/string-hash-compare)一文及學長姐們的共筆後,決定使用 BKDRHash 來進行本次的優化!
<br>
下方是從[各种字符串Hash函数比较](https://www.byvoid.com/zhs/blog/string-hash-compare)中抓到的 **BKDRHash function**。
```C
unsigned int BKDRHash(char *str)
{
unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return (hash & 0x7FFFFFFF);
}
```
>其中```return (hash & 0x7FFFFFFF)```此處有個不解,查資料時有查到說 ```& 0x7FFFFFFF``` 是為了讓其成為 positive integer ,但既然這個 Hash function 的 return type 已經是 unsigned int ,為何還要 ```& 0x7FFFFFFF``` 而不是 ```& 0xFFFFFFFF``` 呢?
>[name=laochanlam][color=blue]
:::danger
盡信書不如無書,你為何不做實驗並分析呢?
認真看 hash value 的分佈 --jserv
:::
<br>
完成了!
要注意的地方是可能會發生碰撞,而且 Table Size的選取也十分重要。
附個小圖:
![Optmize2](http://i.imgur.com/gdhDuUb.png)
做了 Hash 的優化後 findName() 的時間超級快,不過由於在 Hash table 中採用 Link list 來防止碰撞的緣故,append()的時間加長了不少。
:::danger
提出具體縮減 `append()` 時間成本的機制 --jserv
:::
>好
>[name=laochanlam][color=blue]
```
416,554 cache-misses # 41.136 % of all cache refs ( +- 0.51% )
1,012,639 cache-references ( +- 0.50% )
239,848,481 instructions # 1.58 insn per cycle ( +- 0.02% )
152,120,552 cycles ( +- 0.28% )
0.051797760 seconds time elapsed ( +- 0.30% )
```
Cache miss 又減少了!
### Optimize 方案 3: 利用 memory pool 改善 append() 時間
在參考 [ierosodin 的共筆](https://hackmd.io/s/SJ4uqs9Yx#)後,有提到可以用 memory pool 的方法改善 append() 的效能,所以來嘗試一下,先分配一個 ```sizeof( 350000 * entry )``` 大小的連續空間當作 memory pool ,在需要時才向 pool 申請空間。
效能分析附圖:
![Imgur](http://i.imgur.com/JMiRGph.png)
雖說 append() 的時筆是減少了,可是減少的程度遠遠不及[ierosodin 的共筆](https://hackmd.io/s/SJ4uqs9Yx#)中來得多,經過思考後可能是 hash table size 的取值不當導致的,所以嘗試改善 table size 的大小。
<br>
經過一番沒有進展的改善後...才發現我有一個地方少寫一行程式碼...
改回來後:
![Imgur](http://i.imgur.com/oYIg2k7.png)
而且 cache miss 也減少了
```
Performance counter stats for './phonebook_opt_hash_pool' (100 runs):
192,884 cache-misses # 20.701 % of all cache refs ( +- 1.90% )
931,771 cache-references ( +- 0.96% )
160,539,514 instructions # 1.47 insn per cycle ( +- 0.04% )
109,119,512 cycles ( +- 0.71% )
0.038024759 seconds time elapsed ( +- 0.84% )
```
減到 20% 。
<br>
~~總算老懷安慰。~~
### 回答問題環節:
本例選用的 dataset (也就是輸入電話簿的姓名) 是否存在問題?
<br>
### 1. 有代表性嗎?跟真實英文姓氏差異大嗎?
我認為代表性不強,畢竟這個世界上英文姓氏 X Y 作開首的遠遠比其他字母來得少,在搜索算法的資源分配可依據字母出現的頻率作調整;
```
Initial Percentage
a 5.857
b 4.183
c 6.501
d 6.664
e 4.154
f 1.569
g 2.937
h 2.028
i 0.661
j 11.961
k 3.709
l 5.087
m 9.074
n 1.652
o 0.407
p 2.821
q 0.032
r 7.151
s 5.583
t 3.795
u 0.020
v 1.347
w 2.477
x 0.010
y 0.205
z 0.109
```
Source from : [Answers](http://answers.google.com/answers/threadview/id/347668.html)
而且本例子中並沒有考慮名字重覆的情況,像是 1990 年代美國最常見姓氏的比例:
```
Name %
1. Smith 1.006
2. Johnson 0.810
3. Williams 0.699
4. Jones 0.621
5. Brown 0.621
6. Davis 0.480
7. Miller 0.424
8. Wilson 0.339
9. Moore 0.312
10. Taylor 0.311
```
Source from : [Most Common Surnames in the United States 1990](http://surnames.behindthename.com/top/lists/united-states/1990)
但是在現實世界中這情況絕不罕有。
### 2. 資料難道「數大就是美」嗎?
### 3. 如果我們要建立符合真實電話簿情境的輸入,該怎麼作?
- 針對字母頻率不同的情況,我們可以在資料結構或查詢算法中,對字母搜尋及儲存的優先順序作調整,把更多的儲存空間讓給 ```a``` ```o``` 等等常見的字母,提升整體效能。
- 針對姓名相同的情況,我們可以給予多項搜尋資訊的輸入,或是二次搜索的回饋等功能,使查詢者能在"芸芸眾生"中找到要尋找的目標。
---
## 補充知識
- [x] Cache
- [x] Perf
- [x] gnuplot
- [x] astyle
- [x] Makefile
- [x] Hash function
---
## Cache
若要查找的 memory location 在 cache 中, CPU 從 cache 中讀取或寫入數據,就是 **cache hit** ,反之若要查找的 memory location 不在 cache 中,就為 **cache miss** 。
#### 參考及引用資料
[cache 原理和實驗](https://hackmd.io/s/S14L26-_l#)
[cache entry相關資料](http://www.cnblogs.com/miaoyong/p/3416320.html)
---
## 效能分析工具: Perf
安裝完成後我是先把路徑於 `/proc/sys/kernel/perf_event_paranoid` 的值修改掉,不然每次都要sudo很麻煩。
> kernel.perf_event_paranoid 是用來決定你在沒有 root 權限下 (Normal User) 使用 perf 。
> * `2` : 不允許任何量測。但部份用來查看或分析已存在的紀錄的指令仍可使用,如 perf ls、perf report、perf timechart、 perf trace。
> * `1` : 不允許 CPU events data。但可以使用 perf stat、perf record 並取得 Kernel profiling data。
> * `0` : 不允許 raw tracepoint access。但可以使用 perf stat、perf record 並取得 CPU events data。
> * `-1`: 權限全開。
較常用的指令們
- `perf top`
- `perf stat`
- `perf record`
- `perf report`
#### 參考及引用資料
[perf 原理和實務](https://hackmd.io/s/B11109rdg#)
---
## 製圖工具: gnuplot
- 繪圖 ```$ gnuplot [Script]```
- 查看圖片 ```$ eog [jpg/png...]```
更多用法參詳[這裡](https://hackmd.io/s/Skwp-alOg)
#### 參考及引用資料
[gnuplot 語法解說和示範](https://hackmd.io/s/Skwp-alOg)
[gnuplot 读取逗号分隔的数据文件 ](http://blog.csdn.net/liyuanbhu/article/details/8516417)
---
## 程式碼格式化工具: astyle
先來看一下本次作業所要求的規範
```
$ astyle --style=kr --indent=spaces=4 --indent-switches --suffix=none *.[ch]
```
- ```--style=kr``` 用K&R的風格進行編程
- ```--indent=spaces=4``` 四個空格
- ```--indent-switches``` 關於Case的縮排
> Indent 'switch' blocks, so that the inner 'case XXX:'
headers are indented in relation to the switch block.
- ```--suffix=none``` 不保存原始文件
- ``` *.[ch]``` 所有的.c或.h檔
#### 參考及引用資料
[Astyle:代码格式化工具简明指南](http://blog.csdn.net/xiaotao2004/article/details/1560538)
[astyle格式化代码](http://gaylord.iteye.com/blog/2090616)
---
## Makefile
#### 參考及引用資料
[Makefile 語法和示範](https://hackmd.io/s/SySTMXPvl)
---
## Hash function
#### 參考及引用資料
[hash function 觀念和實務](https://hackmd.io/s/HJln3jU_e)
[各种字符串Hash函数比较](https://www.byvoid.com/zhs/blog/string-hash-compare)