# 2016q1 Homework 1 (phonebook) contributed by <`HuangWenChen`> ### Reviewed by `shelly4132` - 可以利用字串壓縮法降低資料表現的成本 - 可利用binary search改寫演算法 ## 開發環境 * Description : Ubuntu 16.04.1 LTS * linux kernel version : 4.4.0-38-generic * CPU : AMD A6-4455M APU with Radeon(tm) HD Graphics * Cache : * L1d cache : 16K * L1i cache : 64K * L2 cache : 2048K 可使用`$ lscpu` and `$ cat /etc/os-release` 查看規格 ## 預期目標 * 準備 GNU/Linux 開發工具 * 學習使用 Git 與 GitHub * 學習效能分析工具 * 研究軟體最佳化 ## Cache 相關知識 * 參考閱讀 * [Shared Level-1 instruction-cache performance on AMD family 15h CPUs](http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/SharedL1InstructionCacheonAMD15hCPU.pdf) * [How L1 and L2 CPU caches work](http://www.extremetech.com/extreme/188776-how-l1-and-l2-cpu-caches-work-and-why-theyre-an-essential-part-of-modern-chips) * [Basic Performance Measurements](http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2012/10/Basic_Performance_Measurements.pdf) There are three categories of cache misses: 1. Compulsory misses - occur on first reference to a data item. 2. Capacity misses - occur when the working set exceeds the cache capacity. 3. Conflict misses - occur when a data item is referenced after the cache line containing the item was evicted. ## Git * 參考閱讀 * [Git 情境劇]( http://blog.gogojimmy.net/2012/02/29/git-scenario/) 將常用的指令先大概看過一遍,上機操做看看。 `git init` 開始一個新的 Git repo. `git status` 確認現在所有檔案的狀況 `git clone` 複製一個專案 `git add` 將想要的檔案加入 Stage `git add .` 將所有編修過的檔案加入 Stage `git commit` 將 Stage 狀態的檔案做 Commit 動作 `git pull remote名稱 branch名稱` 下載一個遠端的 branch 並合併 ==pull = fetch + merge== `git push` 將本地端的 branch 上傳到遠端。 ## 效能分析工具 Perf:Phonebook * 參考閱讀 * [Linux 效能分析工具: Perf](http://wiki.csie.ncku.edu.tw/embedded/perf-tutorial) * [kptr_restrict for hiding kernel pointers]( https://lwn.net/Articles/420403/) 完成前置準備安裝 如果要檢測 cache miss event ,需要先取消 kernel pointer 的禁用。 `$ sudo sh -c " echo 0 > /proc/sys/kernel/kptr_restrict"` 從 [kptr_restrict for hiding kernel pointers]( https://lwn.net/Articles/420403/) 文章裡看到 "If kptr_restrict is set to 0, no deviation from the standard %p behavior occurs." kptr_restrict 設為零可以%p行為發生沒有偏差 ### 未優化版本 `$ make` `$ make run` ``` size of entry : 136 bytes execution time of append() : 0.117969 sec execution time of findName() : 0.009333 sec ``` `$ perf stat -r 100 -e cache-misses,cache-references,instructions,cycles ./phonebook_orig` 當執行要觀看未優化版本的 cache-misses rate 時,發生了奇怪的事情,預期執行後應該會落在 9x%-8x% 之間,但執行之後沒有看到預期的結果,竟然只有 0.530 %,目前尚未了解原因。 `dmidecode` 是可以解析 CPU 型號、主機板型號與記憶體相關的型號等等資訊。 `$ dmidecode -t cache` ``` Handle 0x0029, DMI type 7, 19 bytes Cache Information Socket Designation: L1 CACHE Configuration: Enabled, Not Socketed, Level 1 Operational Mode: Write Back Location: Internal Installed Size: 96 kB Maximum Size: 96 kB Supported SRAM Types: Pipeline Burst Installed SRAM Type: Pipeline Burst Speed: 1 ns Error Correction Type: Multi-bit ECC System Type: Unified Associativity: 2-way Set-associative ``` 發現顯示 L1-cache 是 96kbytes 觀查到 cache-references 非常的大。 ``` size of entry : 136 bytes execution time of append() : 0.103768 sec execution time of findName() : 0.009330 sec Performance counter stats for './phonebook_orig' (100 runs): 368,925 cache-misses # 0.530 % of all cacherefs ( +- 0.80% ) (74.39%) 69,551,729 cache-references ( +- 0.11% ) (51.84%) 262,353,690 instructions # 0.95 insns per cycl ( +- 0.03% ) (76.04%) 276,827,399 cycles ( +- 0.08% ) (74.63%) 0.134035292 seconds time elapsed ( +- 0.10% ) ``` ### 優化版本 * 參考閱讀 [Programming Small](https://hackmd.io/s/S1rbwmZ6) #### 減少entry空間 從參考資料中,了解到在搜尋的過程只需要搜尋 lastName,其他資料內容尚未使用到,所以可以設計一個 struct 只儲存 lastName 使原本的 struct 大小從 136 bytes → 32 bytes ,讓 L1 cache 多放更多資料。 從約30筆entry到約128筆entry ```c= typedef struct __PHONE_BOOK_DETAIL { 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_DETAIL *pNext; } entry_detail; typedef struct __PHONE_BOOK_ENTRY{ char lastName[MAX_LAST_NAME_SIZE]; entry_datail *datail; struct __PHONE_BOOK_ENTRY *pNext; }entry; ``` 將修改完的程式再編譯一次 `$ make` `$ perf stat -r 100 -e cache-misses,cache-references,instructions,cycles ./phonebook_opt` cache-misses rate 問題依舊尚未了解,從時間上與 cache-misses rate 是有改善。但從 cache-misses rate 看沒有很大的變化。 * cache-misses 下降0.2% ``` size of entry : 32 bytes execution time of append() : 0.078245 sec execution time of findName() : 0.009389 sec Performance counter stats for './phonebook_opt' (100 runs): 211,003 cache-misses # 0.330 % of all cache refs ( +- 0.67% ) (73.75%) 63,978,495 cache-references ( +- 0.22% ) (51.55%) 242,944,615 instructions # 1.10 insns per cycle ( +- 0.08% ) (77.21%) 221,482,539 cycles ( +- 0.12% ) (75.64%) 0.107024164 seconds time elapsed ( +- 0.14% ) ``` ![](https://i.imgur.com/xZUT8Sw.png) `$ ./phonebook_hash_opt & sudo perf top -p $!` 將空間減少後時間都花在比對上面 ![](https://i.imgur.com/eEAopUT.png) #### 使用hash function方式 * 參考資料 * [Programming Small](https://hackmd.io/s/S1rbwmZ6) * [各種字符串Hash函數比較](https://www.byvoid.com/zht/blog/string-hash-compare) * [HASHING](https://www.csie.ntu.edu.tw/~hsinmu/courses/_media/dsa_12spring/hashing.pdf) * [Hash table lengths and prime numbers](http://srinvis.blogspot.tw/2006/07/hash-table-lengths-and-prime-numbers.html) * [phonebook code](https://github.com/charles620016/embedded-summer2015/tree/master/phonebook) hash 的方法是將資料透過某種公式(hash function)轉換成 key值,再去bucket中尋找資料。 當沒有overflow的時候,計算hash function的時間: O(1) 而設定 hash table 大小通常會找較大質數,可以讓取出的餘數平均分佈在表中。這裡參考 [Programming Small](https://hackmd.io/s/S1rbwmZ6) 設定 42373 大小。 從 [各種字符串Hash函數比較](https://www.byvoid.com/zht/blog/string-hash-compare) 發現 BKDRHash function 效果最好,所以採用此 hash function。 在處理 static hashing Overflow Handling 有兩種最受歡迎的方式: 1. Open Addressing 2. Chaining 此方法使用 Chaining ```c= 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 & TABLE_SIZE); } ``` 再寫 hash 方式,因為對C不熟再 code 方面研究了很久,看很多 code 慢慢去改寫原本的 code 。 經過 BKDRhash function 優化後 : * append() 時間明顯得上升 * findName() 時間明顯下降為0 * cache-misses 下降 1.7 % `$ make` `$ perf stat -r 100 -e cache-misses,cache-references,instructions,cycles ./phonebook_hash_opt` ``` size of entry : 32 bytes execution time of append() : 0.104041 sec execution time of findName() : 0.000000 sec Performance counter stats for './phonebook_hash_opt' (100 runs): 236,819 cache-misses # 0.360 % of all cache refs ( +- 0.36% ) (73.68%) 65,704,977 cache-references ( +- 0.17% ) (51.50%) 235,583,333 instructions # 1.07 insns per cycle ( +- 0.04% ) (77.26%) 220,478,824 cycles ( +- 0.05% ) (75.60%) 0.106951672 seconds time elapsed ( +- 0.21% ) ``` ![](https://i.imgur.com/oIsOxZj.png) `$ ./phonebook_hash_opt & sudo perf top -p $!` 從下圖可以發現時間幾乎花在 BKDRHash function 上面。 ![](https://i.imgur.com/IMwsgKe.png) 使用第二種 hash function 做比較,選用 Programming Small 提到的 djb2 `$ make` `$ perf stat -r 100 -e cache-misses,cache-references,instructions,cycles ./phonebook_hash2_opt` ``` size of entry : 32 bytes execution time of append() : 0.101210 sec execution time of findName() : 0.000000 sec Performance counter stats for './phonebook_hash2_opt' (100 runs): 207,652 cache-misses # 0.326 % of all cache refs ( +- 0.82% ) (73.63%) 63,632,029 cache-references ( +- 0.13% ) (52.49%) 235,883,099 instructions # 1.10 insns per cycle ( +- 0.05% ) (76.70%) 215,110,824 cycles ( +- 0.37% ) (74.88%) 0.104529298 seconds time elapsed ( +- 0.42% ) ``` 經過 djb2hash function 優化後 : * append() 時間明顯得上升 * findName() 時間明顯下降為0 * cache-misses 下降 2.04% 發現 djb2 cache-misses 下降較多。 ![](https://i.imgur.com/WVcGtpg.png)