# 2017q3 Homework1 (phonebook)
contributed by <`FATESAIKOU`>
### 環境說明
![](https://i.imgur.com/Xn3Dv2r.png)
* CPU: Intel® Core™ i7-6700 CPU @ 3.40GHz
* Memory: 40GB
* Cache:
* L1d/i: 32KB
* L2: 256KB
* L3: 8192KB
### 優化前效能
測試效能前,首先查看範例程式碼當中,是如何進行效能測試,這邊我們看到其中的 main.c。
#### __buildin_clear_cache
首先引起我們的興趣的是從 main.c 56 行開始的:
```clike=
#if defined(__GNUC__)
__builtin___clear_cache((char *) pHead, (char *) pHead + sizeof(entry));
#endif
```
參考了[共筆](https://embedded2016.hackpad.com/ep/pad/static/SS3c9W4KM1S)才發現這是 [gnugcc](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 所提供的 build-in function,目的在於清除之前程式執行時可能留下的指令快取,避免效能測定的不確定性。
#### clock_gettime
接著是 main.c 59 行:
```clike=
clock_gettime(CLOCK_REALTIME, &start)
```
參考[此處](https://starforcefield.wordpress.com/2012/08/06/c%E8%AA%9E%E8%A8%80%EF%BC%9A%E4%BD%BF%E7%94%A8clock_gettime%E8%A8%88%E7%AE%97%E7%A8%8B%E5%BC%8F%E7%A2%BC%E7%9A%84%E6%99%82%E9%96%93%E9%9C%80%E6%B1%82/)的說明,此函式用於計時時十分精準,有機會到達柰秒的精準度等級,我使用參考處提供的程式碼實際測量一次自己電腦的精準度等級:
timer_test.c
```clike=
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
struct timespec tt;
clock_getres(CLOCK_REAL_TIME, &tt);
printf("Resolution: %ld\n", tt.tv_nsec);
return 0;
}
```
結果:
```
Resolution: 1
```
很幸運的,電腦的精細度可以達到 tv_nsec 的最小值,也就是柰秒。
#### append
接著我們在 main.c 第 65 行的位置發現了 append,也就是插入資料進電話簿的部份,接著往深處看發現到其資料結構定義於 phonebook_orig.h 並且實做 append 方法於 phonebook_orig.c:
phonebook_orig.h: entry
```clike=
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;
}
```
可以發現電話簿中每一筆紀錄都十分龐大,並且本身只包含一個指標。
phonebook_orig.c: append
```clike=
enrty *append(char lastName[], entry *e)
{
/* allocate memory for the new entry and put lastName */
e->pNext = (entry *) malloc(sizeof(entry));
e = e->pNext;
strcpy(e->lastName, lastName);
e->pNext = NULL;
return e;
}
```
append 很明顯只是將傳進來的名字放入新分配的空間,接著接上傳進來的節點,並且傳出這個新節點。
main.c:
```clike=
while (fgets(line, sizeof(line), fp)) {
while (line[i] != '\0')
i ++;
line[i - 1] = '\0';
i = 0;
e = append(line, e);
}
```
至此很明顯可以發現此處使用的是單向的 Linked List 作為儲存的資料結構,並且透過這種 append,可以省去插入時搜尋結尾的時間消耗,但同時也必須維護一份 pHead。
#### fineName
接著我們看到了一個實際進行搜尋的部份,位於 main.c 的 88 行,其對 "zyxel" 呼叫了 findName 並且同時為其計時。
```clike=
char input[MAX_LAST_NAME_SIZE] = "zyxel";
...
clock_gettime(CLOCK_REALTIME, &start);
findName(input, e);
clock_gettime(CLOCK_REALTIME, &end);
```
而我們仍然可以在 phonebook_orig.c 當中找到 findName 的實作。
phonebook_orig.c: findName
```clike=
entry *findName(char lastName[], entry *pHead)
{
while (pHead != NULL) {
if (strcasecmp(lastName, pHead->lastName) == 0)
return pHead;
pHead = pHead->pNext;
}
return NULL;
}
```
上面的 findName 採用 linear search 的方式,搜尋整條 pHead 所指向的 Linked List。
#### Report
在 main.c 的第 93 與 96 以及 97 行,程式最後輸出了==建立電話簿資料庫==以及==搜尋字串zyxel==所耗費的時間。
#### make plot
接著可以看看實際產生報表的地方(Makefile plot),循著相依性,發現到其是執行 phonebook_orig 以及將來要優化的 phonebook_opt 各100次,寫入各自的 orig.txt 以及 opt.txt,接著透過 calculate.c 轉換為 output.txt 交給 gnuplot script runtime .gp 進行繪圖,產生報表,但由於 phonebook_opt 的部份因還未開始撰寫,因此去除 phonebook_opt 的報表。
以下便是範例程式產生的報表。
![](https://i.imgur.com/2fYjxDq.png)
讀入的資料庫為 words.txt,總共 349900 筆資料。
#### Bug
另外 main.c 當中還有個值得注意的 bug 位於 99 行
```clike=
if (pHead->pNext) free(pHead->pNext);
free(pHead);
```
很容易可以注意到這樣是記憶體釋放不全。
### 尋找問題
透過上面的 report 我們可以看到這個未優化的版本對於 words.txt 的資料以及搜尋 zyxel 時的效能表現,但這樣的資訊實在太過片面,我們希望能透過同時調整```資料庫大小```、```資料庫內字串長度分佈```以及```搜尋的目標字串長度```,加上觀察 append 以及 findName 的效能表現變化,以及對照 perf 產生的資料,嘗試找出效能低落的原因並改善。
#### make more plot
首先我們看看輸入的 words.txt 上的資料分佈情形:
![](https://i.imgur.com/19uDGsS.png)
上圖是以字串長度配合計數進行繪圖,可以發現其中長度為 8 的字串最常出現,接著開始向兩側遞減。
接著以下針對調整載入的電話簿大小,同時使用常態分佈並調變 $\mu$ 控制載入的字串長度,其中 $\sigma$ 為求作圖方便固定為 $\mu/2$,最後將獲得的資料進行繪圖整理。
* append
![]()
![]()
* findName
![]()
![]()
* cache-misses
![]()
![]()
* cache-references
![]()
![]()
* cache-miss-rate
![]()
![]()
### 優化實驗
接著我們希望能透過一些優化,加速本程式的```載入```以及```查詢```速度。
#### 快取優化
##### structure simplize
##### memory pool
y = (append, findName, alloc speed => cache miss)
x = (db entry num), y = (average string length)
#### 查詢加速
##### hash
##### hash + linked list
##### hash(Soundex) + BST
x = (db entry num), y = (average string length), z = (append, findName, encode speed => collision, cache miss)
(branch predict?)
#### hash table 大小影響
x = (db entry num), y = (hash table size), z = (append, findName => collision, cache miss)
(上述三方法之綜合繪圖)
### dataset 更換
#### Sorted / Not sorted
##### 分布圖 (orig + opt\*3)
y = (accumulation)
x = (db string len)
##### 效能比較
y = (append, findName, encode speed => collision, cache miss)
x = (db entry num)
#### 原生資料 / 現實資料
##### 分布圖
y = (accumulation)
x = (db string len)
##### 效能比較
y = (append, findName, encode speed => collision, cache miss)
x = (db entry num)
### 結果討論
### 參考資料
* [gnuplot 基礎教學系列](https://www.youtube.com/playlist?list=PLgtzOcizoxeuMeNUu_WaL6P2wKlrRSgsj)
* [clock_gettime / clock_getres](https://starforcefield.wordpress.com/2012/08/06/c%E8%AA%9E%E8%A8%80%EF%BC%9A%E4%BD%BF%E7%94%A8clock_gettime%E8%A8%88%E7%AE%97%E7%A8%8B%E5%BC%8F%E7%A2%BC%E7%9A%84%E6%99%82%E9%96%93%E9%9C%80%E6%B1%82/)
* [過去 hackpad 共筆](https://embedded2016.hackpad.com/ep/pad/static/SS3c9W4KM1S)
* [gnugcc](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)