# 2018q1 Homework1 (phonebook)
###### tags:`shaimejump520`
contributed by < `shaimejump520` >
### Reviewed by `bauuuu1021`
* 修正小小筆誤
* 提出大家都沒想過的問題並實際做實驗驗證蠻不錯的,但因此沒實作到 Hash function 及 BST 有點可惜
---
## 開發環境
* OS: ubuntu 16.04 LTS
* 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: 60
* Model name: Intel(R) Core(TM) i5-4200H CPU @ 2.80GHz
* Stepping: 3
* CPU MHz: 2793.471
* CPU max MHz: 3400.0000
* CPU min MHz: 800.0000
* BogoMIPS: 5586.94
* Virtualization: VT-x
* L1d cache: 32K
* L1i cache: 32K
* L2 cache: 256K
* L3 cache: 3072K
* NUMA node0 CPU(s): 0-3
## 開始前準備
* Linux
其實已經使用 ubuntu 好一陣子了,但都沒有認真使用過 vim
看了wiki上的[GNU/Linux 開發工具共筆](https://hackmd.io/c/rJKbX1pFZ)的[終端機,Vim 設定](https://hackmd.io/c/rJKbX1pFZ/https%3A%2F%2Fhackmd.io%2Fs%2FHJv9naEwl) 篇,才發現vim除了簡單的修改外觀、縮排、行數等等之外,還有那麼多可以玩的東西
修改vimrc將實用的操作 mapping 到特定按鍵後,再裝了[Minimalist Vim Plugin Manager](https://github.com/junegunn/vim-plug),vim 使用起來方便了許多,其中最實用的應該是 NERDTree吧,切換檔案速度快上了多,也可以更簡單明瞭的管理檔案
* 剩下的之後再補充吧...
## 基本操作
* 清空快取
```
$ echo 1 | sudo tee /proc/sys/vm/drop_caches
```
當 /proc/sys/vm/drop_caches 的值被設定為1時,是表示要求 Linux 釋放沒在使用的一般性快取(pagecache),而設為2時,則代表要求釋放 dentry 與 inode 所使用到的快取,若設為3則是釋放所有的快取(也就是包含1與2的狀況)
* 執行
執行一次會分別顯示 append() 及 findName() 的執行時間
```
$ sudo ./phonebook_orig
size of entry : 136 bytes
execution time of append() : 0.044309 sec
execution time of findName() : 0.005483 sec
```
使用 makefile 中定義的 cache-test 會執行100次,並計算平均的 cache-misses 等等資訊
```
$ sudo make cache-test
.
.
.
Performance counter stats for './phonebook_orig' (100 runs):
1,261,006 cache-misses # 86.324 % of all cache refs ( +- 0.24% )
1,460,781 cache-references ( +- 0.18% )
263,966,596 instructions # 1.28 insn per cycle ( +- 0.02% )
205,881,476 cycles ( +- 0.14% )
0.063045433 seconds time elapsed ( +- 0.99% )
```
如上,此次的 cache miss 機率高達 86.324%
使用 gnuplot 將資料圖像化以便比較
![](https://i.imgur.com/L2WHTcL.png)
## 問題分析
* 提示可改進方向
* 更改 entry 的 size 節省空間,應可使 cache miss 下降
* Hash function,有效加快搜尋速度至 O(1)
* Binary Serch Tree, 也應能加快搜尋速度至 O(logn)
### 優化一:從Structure著手
[Github link](https://github.com/shaimejump520/phonebook/commit/7e9ccc6ef307e30a55bccfc1524630a2b3e70929)
* 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;
} entry;
```
實際上在 findName() 中僅需搜尋 lastName,並不需傲用到其他的資料,然而該 structure 定義之下足足使用的 136 bytes 的記憶體空間,這在老師的影片中與前賢們的筆記裡都有提到:
> 優化前版本的 Structure size = 136 bytes ,而我的 layer1 cache 也只有 32k bytes 頂多也只能放 32 * 1024 / 136 = 240.94 筆資料,在查找將近 35 萬筆資料時,無可避免的會發生大量 cache-miss,縮小體積後的 struct size = 32 bytes,能放 1024 筆資料,應該能有效減少 cache-miss。[name=Ryan Wang] [2017q1 Homework1 (phonebook)
](https://hackmd.io/iUY7i3c3Ty-GXXQs70Krmw?both)
各位先輩們的解決方法都大同小異,另外新定義一個 structure - detailEntry 將原本搜尋時使用不到的資料定義在其中,而原本的 entry 只存放 lastName 與指向 detailEntry 的 pointer 及 linked-list 所需的 pNext pointer ,如此一來將 entey size 降低到 32 bytes ,應會使 cache-miss rate 下降。
```clike=
typedef struct __PHONE_BOOK_DETAIL_ENTRY {
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];
} detailEntry;
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
detailEntry *detail;
struct __PHONE_BOOK_ENTRY *pNext;
} entry;
```
執行結果如下:
![](https://i.imgur.com/4Steqjd.png)
cache-miss rate 也下降到 39.735%
> 構想中的效能改進有達到,然而以上都是重複先賢們的實驗而已,在此過程中我特別注意到,大家都可以明瞭的說原始 structure 設計 size 為 136 bytes,而我卻對此疑惑不已,這也是我進行以下探討與實驗的緣由 [name=shaimejump520]
### 問題討論 -- Structure Size
* 背景知識
* size of char = 1 bytes
* size pointer = 4 or 8 bytes
( pointer 中儲存的是 memory address,所以在不同位元的電腦中,其 size 應該不相同,我的開發環境是 64 bits,所以應該為 8 bytes)
小測試:
```clike=
int main() {
char *ptr;
printf("%d\n", sizeof(ptr));
return 0;
}
```
輸出結果:8
* Size 問題:
原始結構中,若將各資料量大小相加,結果應為 131 bytes,然而 sizeof(entry) 的輸出值卻是 136,我為此感到蠻納悶的,後來發現有 Alignment 這件事,因為事關 memory access 的效能問題,所以 compiler 傾向將資料對齊,此舉能提升資料存取的效率
```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;
} entry;
```
與 131 最接近的 8 的倍數正好是 136,所以我猜測 compiler 會自己將資料對齊至 8 bytes 也就是 64 bits
* 驗證 -- 猜測錯誤,並非總是對齊 8 bytes
為了證實上面的猜測,我做了個小測試:
```clike=
typedef struct {
char a;
char b;
} s;
int main() {
printf("%d\n", sizeof(s));
return 0;
}
```
輸出結果是:2
所以我原先的猜測是錯誤的,並非總是對齊 8 bytes
* 經過多次實驗,發現會根據排列順序對下方的宣告型態做對齊
```clike=
typedef struct {
char a; //對齊b,所以佔 4bytes
int b;
} s;
typedef struct {
char a; //對齊b,所以佔 4bytes
int b; //a與b總共佔 8bytes,正好與 pointer 對齊
int c; //對齊ptr,所以佔 8bytes
int *p;
} t;
int main() {
printf("%d ", sizeof(s));
printf("%d\n", sizeof(t));
return 0;
}
```
輸出結果為:8 24
圖形解說:(空白對齊所需空置space)
structure s :
| a | | | |
| - | - | - | - |
| b | b | b | b |
structure t :
| a | | | | b | b | b | b |
| - | - | - | - | - | - | - | - |
| c | c | c | c | | | | |
| p | p | c | p | p | p | p | p |
>[color=red]表格 (3,3) 應為 p 而不是 c [name=bauuuu1021]
* 有趣的是,不同的排列順序其 size 可能不同
```clike=
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
char firstName[16];
char email[16];
char phone[10];
struct __PHONE_BOOK_ENTRY *pNext; //將pNext移動至此宣告
char cell[10];
char addr1[16];
char addr2[16];
char city[16];
char state[2];
char zip[5];
} entry;
```
輸出:size of entry : 144 bytes(與原先的 136 bytes 不同)
Q:為什麼 compiler 不將資料順序 reorder 再做對齊,以取得最小的空間使用呢?
A:因為 compiler 無法確認 struct 是何種儲存結構 (for example: a foreign library, a file on disc, network data, CPU page tables, ...),compiler 並不知道這些結構的編碼(是以哪種方式儲存的),如過貿然將資料重排,可能產生與原本不相同的結構,造成資料的不一致。舉例來說,ZIP file的儲存結構跟其壓縮方式有關,若任意的將資料 reorder 將導致資料遺失或者是錯誤。[參考資料](https://stackoverflow.com/questions/9486364/why-cant-c-compilers-rearrange-struct-members-to-eliminate-alignment-padding)
* 結論 --- 適當的排列 structure 結構也能減少程式執行時的空間浪費