# 2016q3 Homework1 (phonebook) contributed by <`RayPan`> ### Reviewed bt `finalallpass` * 因為沒有使用細部資料,可以考慮重建structure時不需多用一個指標指回細部資料。可以再減少8bytes的entry size。 * 可能可以分析一下為何djb2和BKDR的cache-misses的差異從何而來。 * 修改structure的部分可以補上cache reference的結果 --- ## 開發環境 ![](https://i.imgur.com/RWxX7Ge.png) 第一次使用linux作業系統 花了不少時間研究指令 * CPU ``` raypan@raypan-X550JD:~$ lscpu 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: 2782.281 CPU max MHz: 3400.0000 CPU min MHz: 800.0000 BogoMIPS: 5587.62 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 3072K NUMA node0 CPU(s): 0-3 ``` >> 請考慮升級到 Ubuntu 16.04,不然某些軟體套件會太舊 [name=jserv] 升級16.04版 `$ sudo apt-get update && sudo apt-get dist-upgrade` `$ sudo reboot` `$ sudo update-manager -d` ![](https://i.imgur.com/Ua8zf3E.png) --- ## GitHub * 簡介 是一個基於網站基礎的託管服務 (hosting service)平台,它提供了以Git為唯一的版本控制系統。 * 常用指令 `$ git init`:要開始使用 Git 你必須先建立一個 Git 的 Repository。 `$ git remote add origin git@github.com:your_account/your_repository.git`:可以連結到遠端的repository。 `$ git clone git@github.com:Username/repository.git`:可將遠端的repository複製一份到本機,若遇防火牆阻隔問題,請改用HTTPS通訊協定:`https://github.com/...` `$ git remote -v`:查看working directory所連結的遠端repository。 `$ git status`:查看目前working directory中所有檔案的情形。 `$ git add`:將未被git追蹤的檔案加入追蹤,可最後加上`.`追蹤全部檔案。 `$ git commit`:將檔案提交。 `$ git push`:上傳到遠端repository。 `$ git pull`:從遠端更新 --- ## 效能分析工具-Perf 應用程式可以利用 PMU (Performance Monitoring Unit), tracepoint 和核心內部的特殊計數器 (counter) 來進行統計,另外還能同時分析運行中的核心程式碼 * 常用指令 `perf list` : 印出perf可以觸發哪些event `perf top` : 找出拖慢系統的熱點 `perf stat` : 已經有個要優化的目標,對這個目標進行特定或一系列的event檢查,進而了解該程序的效能概況。 `perf stat -r 10 cache-misses ./example`表示對特定程式example執行10次perf其cache-misses `perf record` :針對函式級別進行 event 統計,方便我們對程序「熱點」作更精細的分析和優化。 參考資料:[成大wiki-Linux效能分析工具:Perf](http://wiki.csie.ncku.edu.tw/embedded/perf-tutorial) --- ## Makefile * #### 規則 `target: dependencies` `<tab>command` * target: 所要建立的檔案。 * dependencies: 相依項目,make會依據此決定是否要重新編譯targey。 * commands:建立targets的指令。 注意:make 在編譯時,若發現 target 比較新,也就是 dependencies 都比 target 舊,那麼將不會重新建立 target,如此可以避免不必要的編譯動作。 `.PHONY`: 指定後面的為fake項目,利用`.PHONY: clean`來指定clean為fake項目,被視為要建立clean這個檔案,但永不被執行。 * #### 變數宣告 `MACRO = value` : 可利用`$(MACRO)`或`${MARCO}`來存取已定義的變數。 `:=` : 變數的值決定於它在Makefile中的位置,而不是整個Makefile展開後最終的值。 `?=` : 若變數未定義,則替它指定新的值。否則採用原有的值。 * #### 內部變數 `$?` : 已被更新的dependencies的值,i.e.dependencies中,比targets還新的值。 `$@` : targets的值。 `$<` : 第一個dependencies的值。 `$*` : targets所指定的檔案,但不包含副檔名。 * #### 特別字元 `@` : 不要顯示執行的指令在螢幕上。 `-` : 即使該行指令出錯,也不會中斷執行。 `-DMACRO`: 指定一個MARCO的名字。 * 參考資料: [Makefile 語法簡介](http://tetralet.luna.com.tw/?op=ViewArticle&articleId=185)、 [猴子都會寫的Makefile](http://mropengate.blogspot.tw/2015/06/makefile-makefile.html) --- >> 張貼程式碼時,請注意縮排 (空白,而非 tab,與原本程式碼一致的風格) 並且避免單行後方非必要的空白 [name=jserv] >> 延伸閱讀: [The Lost Art of C Structure Packing](http://www.catb.org/esr/structure-packing/) [name=jserv] --- ## 可能的效能改進方法 * 改寫`struct __PHONE_BOOK_ENTRY` * 使用 Hash function 加速查詢 * 使用字串壓縮的演算法,降低資料表示的成本 * 使用 binary search tree 改寫演算法 --- ## 改善過程 #### 未修改之前 ``` size of entry : 136 bytes execution time of append() : 0.053688 sec execution time of findName() : 0.004984 sec ``` ``` Performance counter stats for './phonebook_orig' (100 runs): 1,003,292 cache-misses # 84.258 % of all cache refs ( +- 0.32% ) 1,190,732 cache-references ( +- 0.20% ) 260,911,327 instructions # 1.39 insns per cycle ( +- 0.02% ) 187,778,382 cycles ( +- 0.19% ) 0.057238264 seconds time elapsed ( +- 0.60% ) ``` ### 1. 修改`struct __PHONE_BOOK_ENTRY` 由main.c可知 ```c=75 #ifndef OPT e = pHead; assert(findName(input, e) && "Did you implement findName() in " IMPL "?"); assert(0 == strcmp(findName(input, e)->lastName, "zyxel")); #endif ``` 程式僅透過lastname來查找 其他聯絡人資訊可以放入另一個結構中 如有需要 透過pDetail來索取額外聯絡人資訊 可以減少entry的大小達到優化 修改後程式碼如下 ```c=9 typedef struct __PHONE_BOOK_DETAILS{ 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]; }details; typedef struct __PHONE_BOOK_ENTRY { char lastName[MAX_LAST_NAME_SIZE]; struct __PHONE_BOOK_DETAILS *pDetails; struct __PHONE_BOOK_ENTRY *pNext; } entry; ``` 執行看看 在執行程式之前先清空cache: `$ echo 1 | sudo tee /proc/sys/vm/drop_caches` ``` size of entry : 32 bytes execution time of append() : 0.037598 sec execution time of findName() : 0.004964 sec ``` 如此entry的大小變可縮小至32bytes ``` Performance counter stats for './phonebook_opt' (100 runs): 135,386 cache-misses # 44.511 % of all cache refs ( +- 0.63% ) 304,164 cache-references ( +- 0.45% ) 244,033,296 instructions # 1.94 insns per cycle ( +- 0.02% ) 125,671,746 cycles ( +- 0.20% ) 0.038329599 seconds time elapsed ( +- 0.50% ) ``` 可以發現cache-misses由原本的84.258%下降至44.511% 接著使用makeplot繪製效能比較圖 ***由於main.c中83行,OPT存在才能輸出opt.txt,所以在phonebook.h中必須#define OPT 1*** ```c=83 #if defined(OPT) output = fopen("opt.txt", "a"); ``` 繪圖如下 ![](https://i.imgur.com/7yVWp7r.png) >> 避免用圖片來表達文字輸出 [name=jserv] >> 不要只做執行時間的比較,要對照看 cache reference 的差異,拿出你的專業來 [name=jserv] ### 2.使用Hash function 加速查詢 * #### (1)使用djb2 hash函式 參考共筆:[rni c](https://embedded2016.hackpad.com/ep/pad/static/9eSkToGwJvZ) 捨棄原本的sequential search 首先利用`hashInitial()`建立hash table hash table size訂為65537(質數) 結構hashTable內包含`tableSize`及`list` ```clikec=9 hashTable *hashInitial(){ hashTable *ht = NULL; ht = (hashTable *)malloc(sizeof(hashTable)); ht->list = (entry **)malloc(sizeof(entry *)*HASHTABLE_SIZE); for (int i = 0; i < HASHTABLE_SIZE; i++) ht->list[i] = NULL; return ht; } ``` 注意:選擇hashTable大小為質數的原因為 避免hash collison(輸入字串不同,雜湊值相同) * hashFunction使用Dan Bernstein提出的[djb2](http://www.cse.yorku.ca/~oz/hash.html)hash函式 ```c=21 unsigned int hashFunction(char *lastName){ unsigned int hashVal = 0; while (*lastName != '\0') hashVal = (hashVal << 5) + *lastName++; return hashVal % HASHTABLE_SIZE; } ``` 改寫append函式 可以注意到由於findname長度已定義在MAX_LAST_NAME_SIZE 所以將原本`strcpy`改為`strncpy` 可省去比對其他無效字元。 ```clikec=28 int hashAppend(char *lastName, hashTable *ht){ entry *newEntry; newEntry = (entry *)malloc(sizeof(entry)); strncpy(newEntry->lastName, lastName, MAX_LAST_NAME_SIZE); newEntry->pNext = ht->list[hashFunction(lastName)]; ht->list[hashFunction(lastName)] = newEntry; return 0; } ``` ``` size of entry : 32 bytes execution time of append() : 0.054746 sec execution time of findName() : 0.000008 sec ``` 可以發現到 由於須另外建構一個hashtable append的執行時間會略為上升 但查找時間會大幅下降 ``` Performance counter stats for './phonebook_opt' (100 runs): 151,896 cache-misses # 38.517 % of all cache refs ( +- 2.53% ) 394,365 cache-references ( +- 0.76% ) 298,128,462 instructions # 1.70 insns per cycle ( +- 0.03% ) 175,143,354 cycles ( +- 0.53% ) 0.053467373 seconds time elapsed ( +- 0.82% ) ``` cache misses也下降到了38.517% ![](https://i.imgur.com/OiKuT1F.png) * #### (2)使用BKDR hash函式 參考老師提供的[Programming Small](https://hackmd.io/s/S1rbwmZ6) ```c=29 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 % HASHTABLE_SIZE; } ``` ``` size of entry : 32 bytes execution time of append() : 0.052487 sec execution time of findName() : 0.000008 sec ``` 執行時間大略相同 ``` Performance counter stats for './phonebook_opt' (100 runs): 106,166 cache-misses # 26.940 % of all cache refs ( +- 1.09% ) 394,080 cache-references ( +- 0.26% ) 298,896,581 instructions # 1.71 insns per cycle ( +- 0.03% ) 174,642,726 cycles ( +- 0.57% ) 0.052549821 seconds time elapsed ( +- 0.57% ) ``` 但會發現cache-misses可以再下降到26.397% --- ## 其他筆記 #### 關於前置處理器#if defined 若有兩個c檔案同時include同一個標頭檔,但要同時編譯成一個檔案,會造成衝突。 ``` #if defined(ABC) or #ifdef(ABC) //若ABC有定義過(i.e. #define) 程式1 則編譯程式1(非執行) #else 程式2 否則編譯程式2 #endif ``` 參考資料:[ #ifndef#define#endif的用法(整理) 原作者:icwk ](http://huenlil.pixnet.net/blog/post/24339151-%5B%E8%BD%89%5D%23ifndef,-%23define,-%23endif%E7%9A%84%E7%94%A8%E6%B3%95(%E6%95%B4%E7%90%86)-) #### 關於gcc參數 `-c` : 只做編譯並生成目標文件。 `-g` : 編入除錯資訊(使用gdb時必要)。 `-O0` : 不進行優化(注意:是英文字母大歐+零)。 `-Wall` : 顯示所有警告訊息。 #### 關於指標大小 32bits的電腦,指標大小為4bytes 64bits的電腦,指標大小為8bytes --- 作業詳細資訊:[A01: phonebook](https://hackmd.io/s/S1RVdgza) ###### tags:`RayPan` `A01:Phonebook`