# 2017q1 Homework1 (phonebook)
contributed by <`ierosodin`>
[github](https://github.com/ierosodin/phonebook)
### Reviewed by `janetwei`
* 電話簿是否做到動態資料新增和刪除
* 還有其他的方法可以實驗,如:字串壓縮法
* 還未引進符合現實狀況的 data set實驗
## 開發環境
作業系統 : elementary OS loki
`$ lscpu`
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 12
On-line CPU(s) list: 0-11
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 45
Model name: Genuine Intel(R) CPU @ 3.30GHz
Stepping: 5
CPU MHz: 2101.558
CPU max MHz: 3900.0000
CPU min MHz: 1200.0000
BogoMIPS: 6600.00
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 15360K
NUMA node0 CPU(s): 0-11
## Astyle
To format your file you can execute below command:
`$ astyle --style=kr --indent=spaces=4 --indent-switches --suffix=none *.[ch]`
## 未優化效能分析
`$ perf stat --repeat 100 -e cache-misses,cache-references,instructions,cycles ./phonebook_orig`
```
199,0010 cache-misses # 90.561 % of all cache refs ( +- 0.07% )
219,7433 cache-references ( +- 0.07% )
2,6118,9558 instructions # 1.24 insns per cycle ( +- 0.02% )
2,1139,7062 cycles ( +- 0.38% )
```
`$ ./phonebook_orig & perf top -p $!`
```
29.23% libc-2.23.so [.] __strcasecmp_l_avx
17.38% phonebook_orig [.] findName
10.93% phonebook_orig [.] main
```
## 優化
### 縮減 struct 大小
從 `$ perf top` 中可以發現,程式的熱點在 __strcasecmp_l_avx ,也就是花費許多的時間在比較字串,然而 findName() 中,字串的比較只使用到 entry 結構的 lastName,但程式每次在 cache data 時,卻要 cache 整個 136 bytes 的 entry,這也造成大量的 cache misses ,因此嘗試重寫 entry 的結構,將 append() 與 findName() 不會用到的資料獨立出來,增進程式 cache 時的效能。
```clike=
typedef struct __PHONE_BOOK_INFO {
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];
} info;
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
struct __PHONE_BOOK_INFO *info;
struct __PHONE_BOOK_ENTRY *pNext;
} entry;
```
```
33,4150 cache-misses # 50.126 % of all cache refs ( +- 0.66% )
66,6618 cache-references ( +- 0.20% )
2,4425,0894 instructions # 1.65 insns per cycle ( +- 0.02% )
1,4798,1744 cycles ( +- 0.19% )
```
![](https://i.imgur.com/5VO0bSF.png)
### 使用 hash function (BKDR)
從 `$ perf top` 中可以發現, findName() 花費了相當多的時間,嘗試在 append() 中使用 hash function ,加速 findName() 的效能。
參考 [Hash function](http://algs4.cs.princeton.edu/34hash) 中的 Hash Tables
>有非常多不同的 hash function,請問是哪一個呢?
>[name=課程助教][color=red]
>>這裡使用了 BKDR Hash Function ,有時間再來比較不同 function 的效能比較XD[name=ierosodin][color=green]
```clike=
int hash_number = 0;
for (int i = 0; i < s.length(); i++)
hash_number = (seed * hash_number + s.charAt(i));
return hash_number %= SIZE;
```
perf stat:
```
45,4762 cache-misses # 31.637 % of all cache refs ( +- 0.52% )
143,7442 cache-references ( +- 0.07% )
3,5411,1494 instructions # 1.51 insns per cycle ( +- 0.15% )
2,3525,5300 cycles ( +- 0.31% )
```
從效能分析圖中可以發現, findName() 的時間有大幅度的下降,不過相對的, append() 必須要額外花費時間。
![](https://i.imgur.com/SIn0TJX.png)
#### 比較不同 hash table size 對效能的影響
不同的 table 大小,會影響資料的分佈,越大的 size ,可以使每一個 hash number 的 linked-list 變短,加速 findName() 的速度。
這裡以 lastName = odontomous 為例,比較不同 size 對搜尋速度的影響:
| table size | search time |
|:------:|:------:|
|17|0.000267s|
|33|0.000151s|
|997|0.000004s|
|3571|0.000001s|
|9137|0.000001s|
![](https://i.imgur.com/wtkKmBy.png)
![](https://i.imgur.com/mc5MflJ.png)
![](https://i.imgur.com/b6r0vsx.png)
![](https://i.imgur.com/Ux0Z37J.png)
![](https://i.imgur.com/ACFeyV3.png)
可以發現,當 table size 越大時,點的分佈會越均勻, slot 數下降,而搜尋速度也會更快。
#### 比較使用不同的 hash function
* ELF Hash Function
![](https://i.imgur.com/fD5ub8c.png)
* P. J. Weinberger Hash Function
![](https://i.imgur.com/NDw8ukE.png)
* AP Hash Function
![](https://i.imgur.com/5Hfvk8k.png)
* SDBM Hash Function
![](https://i.imgur.com/qK3tWpn.png)
* RS Hash Function
![](https://i.imgur.com/HKx5exX.png)
* JS Hash Function
![](https://i.imgur.com/reI3dU2.png)
* BKDR Hash Function
![](https://i.imgur.com/RRqTnra.png)
>只看 slot 數好像不容易比較不同方法間的效能差異,沒有找到一個比較適當的量測方式@@[name=ierosodin][color=green]
~~### OpenMP~~
~~在建立 linked-list 時,需要花費許多時間在 for 迴圈,可以利用 OpenMP 進行平行化的處裡,增進 append() 的效能。~~
>append() 若沒有謹慎的使用平行化處理,會造成 race condition ,使 linked-list 的內容發生遺失。[name=ierosodin][color=green]
### Memory pool
:::success
Wikipedia : 預先規劃一定數量的記憶體區塊,使得整個程式可以在執行期規劃 (allocate)、使用 (access)、歸還 (free) 記憶體區塊
:::
>在寫這段筆記前,必須要說... memory pool 太神啦!!!深深體會到,好的記憶體使用,可以大大提升效能[name=ierosodin][color=green]
比起原先在每次需要配置記憶體才使用 malloc() , memory pool 主要的目的就是先配置好一大串連續的記憶體,之後要使用時再分配。不過在釋放記憶體的部分要非常小心,可以利用 valgrind 作為驗證的工具。
```clike=
m_pool *pool_allocate(int size)
{
m_pool *pool = (m_pool *) malloc(sizeof(m_pool));
pool->head = pool->next = (char *) calloc(1, size);
pool->tail = pool->head + size;
return pool;
}
void *pool_access(m_pool *pool, int size)
{
if (pool->tail - pool->next < size)
return NULL;
void *tmp = pool->next;
pool->next = pool->next + size;
return tmp;
}
void pool_free(m_pool *pool)
{
free(pool->head);
free(pool);
}
```
>這只是比較簡單的 memory pool 實作,如果一開始配置的記憶體大小不夠使用,將會造成 segmentation fault,在此實作中沒有做相對應的處裡。[name=ierosodin][color=green]
從下面的效能比教圖可以發現,使用 memory pool 後,append() 的時間下降許多:
![](https://i.imgur.com/NOoKAdm.png)
而觀察優化後的 perf 分析可以發現,整體的 cache-misses 數量下降了!
```
13,1139 cache-misses # 29.716 % of all cache refs ( +- 1.66% )
44,1314 cache-references ( +- 0.27% )
2,4768,6310 instructions # 1.78 insns per cycle ( +- 0.21% )
1,3943,8197 cycles ( +- 0.52% )
0.090425028 seconds time elapsed ( +- 1.58% )
```
### GCC optimization
對原程式碼做 compilier 的 -Ofast 最佳化,結果可以發現,我們做的優化已經超越 compiler 啦~
![](https://i.imgur.com/NSGBPGp.png)
再對所有方法做一次 -Ofast 的最佳化:
![](https://i.imgur.com/k34kiVY.png)
## Memory leak
為了確認使用完的 Linked-list 的記憶體空間是否被釋放,利用 valgrind 工具來檢測使用 memory 的狀況。
檢查 phonebook_orig:
`$ valgrind --leak-check=full ./phonebook_orig`
結果:
```
==21434== HEAP SUMMARY:
==21434== in use at exit: 47,586,264 bytes in 349,899 blocks
==21434== total heap usage: 349,906 allocs, 7 frees, 47,596,856 bytes allocated
```
可以明顯看到,程式執行過程中,總共 alloc 了 349,906 次,卻只有釋放 7 次記憶體!
修改 main.c 中釋放記憶體的部份:
```clike=
entry *tmp;
while ((tmp = pHead) != NULL) {
pHead = pHead->pNext;
free(tmp);
}
```
重新檢查:
```
==22359== HEAP SUMMARY:
==22359== in use at exit: 0 bytes in 0 blocks
==22359== total heap usage: 353,476 allocs, 353,476 frees, 11,321,392 bytes allocated
==22359==
==22359== All heap blocks were freed -- no leaks are possible
```