# 2017q3 Homework1(phonebook) contributed by <`loolofen`> ## 預期目標 * 準備 GNU/Linux 開發工具 * 學習使用 Git 與 GitHub * 學習效能分析工具 * 研究軟體最佳化 ## 開發環境 使用 lscpu 查看CPU、快取內容 ``` Ubuntu 16.04.1 LTS CPU:Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 6144K ``` ## Phone Book ### 未優化版本 原本 phonebook 的每個 entry 的大小為 136bytes 而使用的電腦為 L1 Cache 為 32KB , 所以可存資料為 $32K/(138*8)=30.12$,代表裏面只能存 30 筆左右。在進行35萬筆資料比對時,會造成大量的cache miss。 ``` ./phonebook_orig ``` ``` size of entry : 136 bytes execution time of append() : 0.038199 sec execution time of findName() : 0.006569 sec ``` Performance counter stats for './phonebook_orig' (100 runs): ``` 125,1543 cache-misses # 87.177 % of all cache refs ( +- 0.04% ) 143,5629 cache-references ( +- 0.05% ) 2,6206,9048 instructions # 1.16 insn per cycle ( +- 0.02% ) 2,2534,1157 cycles ( +- 0.20% ) 1.0.069925365 seconds time elapsed ( +- 0.25% ) ``` ### 優化版本 #### 第一種優化:減少entry 在`phonebook_orig.c`中,可以發現每次進行`findName()`時,只用到`lastName`,卻須將整個entry 136 byte載入cache中。 ```clike= entry *findName(char lastName[], entry *pHead) { while (pHead != NULL) { if (strcasecmp(lastName, pHead->lastName) == 0) return pHead; pHead = pHead->pNext; } return NULL; } ``` 其實findName跟append都只用到lastName,所以我們其實可以把那些多餘的訊息都去掉不要,因此我新增了一個比較簡單的struct。 ```clike= typedef struct __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; ``` 並用一指標`*detailInfo`指向它。 ```clike= typedef struct __PHONE_BOOK_ENTRY { char lastName[MAX_LAST_NAME_SIZE]; detail *detailInfo; struct __PHONE_BOOK_ENTRY *pNext; } entry; ``` ``` ./phonebook_opt ``` ``` size of entry : 32 bytes execution time of append() : 0.033703 sec execution time of findName() : 0.002436 sec ``` 從下面的資訊可以看到,只是單純的把entry的大小減小,就大幅的降低了 cache miss ,從原本的 87.177 % 降低到了 48.673 % ,效果非常驚人。 ``` Performance counter stats for './phonebook_opt' (100 runs): 22,5903 cache-misses # 48.673 % of all cache refs ( +- 1.01% ) 46,4121 cache-references ( +- 0.73% ) 2,4492,4206 instructions # 1.87 insn per cycle ( +- 0.02% ) 1,3093,8592 cycles ( +- 0.25% ) 0.042957874 seconds time elapsed ( +- 0.55% ) ``` ![](https://i.imgur.com/wXOz4wp.png) #### 第二種優化: hash function 參考:[BKDRHash](http://www.cnblogs.com/liuliuliu/p/3966851.html)、[petermouse](https://hackmd.io/s/SJEbssma#) 以下為 BKDR_hash 。 ```clike= unsigned int BKDR_hash(char lastName[], int hash_table_size) { unsigned int seed = 131; unsigned int hash = 0; int i = 0; while(lastName[i] != '\0') { hash = hash * seed + lastName[i]; i++; } return hash %= hash_table_size; } ``` 我們藉由改變他的 hash_table_size 看有什麼差別。 當 hash table size = 300 時 ``` size of entry : 32 bytes execution time of append() : 0.043916 sec execution time of findName() : 0.000033 sec ``` 當 hash table size = 1500 時 ``` size of entry : 32 bytes execution time of append() : 0.055376 sec execution time of findName() : 0.000002 sec ``` 當 hash table size = 2000 時 ``` size of entry : 32 bytes execution time of append() : 0.057486 sec execution time of findName() : 0.000003 sec ``` 看起來 'append()'的執行時間,隨著 hash table size 增大而變大,也比位優化版本的還大,但是 'findName' 的時間隨著 hash table size 增大而變小的趨勢,與未優化版本比較少了許多。 ``` 24,3102 cache-misses # 46.631 % of all cache refs ( +- 1.58% ) 52,1337 cache-references ( +- 0.29% ) 2,5526,5485 instructions # 1.43 insn per cycle ( +- 0.02% ) 1,7872,5769 cycles ( +- 0.50% ) 0.057909914 seconds time elapsed ( +- 0.55% ) ``` cache miss 降到 46.631% 左右,和縮小 entry 的優化版本 (48%) 稍微低一點。 以下為所有方式比較圖,可以看出 BKDR_hash 總執行時間比未修改的還久,縮小entry 執行時間最短, ![](https://i.imgur.com/o0oxGvb.png) ## 參考資料 [BKDRHash](http://www.cnblogs.com/liuliuliu/p/3966851.html) [petermouse](https://hackmd.io/s/SJEbssma#) [作業區1](https://hackmd.io/OwDghgDApgRsAsBaAZgRmMR8CsAmAJogJyrYyJ4DGc8uwYRRMQA=#)