# 2017q1 Homework1 (phonebook) ###### tags: `embedded` contributed by <`Cayonliow`> tags: `perf` `gnuplot` `astyle` `phonebook` `hash` `cache` ### Reviewed by `yanang` * 在改變資料結構情況下 (參考 [劉亮谷的共筆](https://hackmd.io/MYZgnCwGbALAtGArCADPWAjAhgRnpgOwAmY8UAbMJqsLgBxb2ZA=?both)),可以考慮將資料宣告為 pointer ,在確定有資料進入後在配置記憶體。 * 使用 hash 的情況下,除了不同的 hash function 以外, size of hash table 也會影響結果。可以嘗試使用不同的 table 大小去觀察實驗結果。 ### Reviewed by `ryanwang522` * Commit message 可以不用註明日期,github 會有紀錄。另外 Commit 的內容目的以及如何達到可以寫在 message 的內文,這樣日後回頭看也會比較摸得著頭緒,==The future maintainer that thanks you may be yourself!== * 可以嘗試 Binary Search Tree 的方法提昇效能,注意 Cache line 的大小也是需要考慮的因素,參考 [關於 CPU Cache](http://cenalulu.github.io/linux/all-about-cpu-cache/)。 ### Reviewed by `SeanCCC` * hash table的大小跟查詢效率有直接相關,建議嘗試各種大小。 --- ## 作業 * 題目: [B01: phonebook](https://hackmd.io/s/rJYD4UPKe#) * github(原來的): [phonebook](https://github.com/sysprog21/phonebook/) * hackmd(示範)][示範用的 Phonebook 作業](https://hackmd.io/s/BJRMImUPg#) ### 開發環境 ```shell= cayon@cayon-X550JX:~$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 60 Model name: Intel(R) Core(TM) i7-4720HQ CPU @ 2.60GHz Stepping: 3 CPU MHz: 2423.484 CPU max MHz: 3600.0000 CPU min MHz: 800.0000 BogoMIPS: 5188.45 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 6144K NUMA node0 CPU(s): 0-7 ``` ## 工具 * Perf * Linux 效能分析工具 * 文章: [perf 原理和實務](https://hackmd.io/s/B11109rdg#) * gnuplot * 制圖工具 * 文章: [gnuplot 語法解說和示範](https://hackmd.io/s/Skwp-alOg#) * Astyle * <s>代码</s>程式碼格式化工具 * 我們現在要用的的是 K&Rstyle * 注意: `$ astyle --style=kr --indent=spaces=4 --indent-switches --suffix=none *.[ch]` * *.[ch] 代表全部的意思, 就不用個別檔案改 * 改了之後要重新 `git add .` >> "code" 台灣/繁體中文的慣用說法是「程式碼」,請留意 [name="jserv"] ## 原理與概念 * cache * 文章: [cache 原理和實驗](https://hackmd.io/s/S14L26-_l#) * Hash * 之前上資訊安全課程的時候有接觸過 * 文章: [散列函數 (wikipedia) ](https://zh.wikipedia.org/wiki/%E6%95%A3%E5%88%97%E5%87%BD%E6%95%B8) , [hash function 觀念和實務](https://hackmd.io/GwRg7AzAJgxgDATgLQCMERkgLFgZr1ADhGRilwEMz9CAmFQoA===?view#) , [哈希表之bkdrhash算法解析及扩展 ](http://blog.csdn.net/wanglx_/article/details/40400693) , [各種字符串Hash函數比較](https://www.byvoid.com/zhs/blog/string-hash-comparev) ## 開發記錄 * 20-23 Feb * 開學第一周,什麼都不會,就先把老師的專案 clone 下來看,然後也弄了雙系統,開始試用在 linux 的環境下作業 * 去研究要用到的開發工具 * perf * 常常忘記權限問題(sudo) * gnuplot * 位移的部分(histogram)還是有一點不懂,還在試 * Astyle * 之前的 coding style 不好,在改了 * 可是 git hook 一直弄不好 * 24 Feb * 突然發現 clone 到老師的專案,這樣會 push 錯地方,所以重新 fork 了一次 * 把 hackmd 再弄了一次,之前的太亂了 * 25 Feb * 研究 cache * 第一個優化版本, 參考 [示範用的 Phonebook 作業](https://hackmd.io/s/BJRMImUPg#) ## 原始版本 * 執行 phonebook_orig , 得知 `append()` , `findName()` 的時間 ``` $ ./phonebook_orig ``` * 結果: ``` size of entry : 136 bytes execution time of append() : 0.036544 sec execution time of findName() : 0.004856 sec ``` * 檢查 cache misses ``` $ make cache-test ``` * cache misses 是 85.730% ``` Performance counter stats for './phonebook_orig' (100 runs): 89,3575 cache-misses # 85.730 % of all cache refs ( +- 1.33% ) 104,2318 cache-references ( +- 1.54% ) 2,6095,1819 instructions # 1.38 insns per cycle ( +- 0.02% ) 1,8911,4788 cycles ( +- 0.75% ) 0.054679129 seconds time elapsed ( +- 1.12% ) ``` * 透過 perf 找熱點: ``` $ ./phonebook_orig & sudo perf top -p $! ``` * 結果:![](https://i.imgur.com/19dYSz7.png) >避免用圖片表示文字資訊,直接複製終端機資訊貼上來 >[name=課程助教][color=red] >好的, 已修正,謝謝助教 >[name=Cayon][color=green] ``` 23.07% libc-2.23.so [.] __strcasecmp_l_avx 21.91% phonebook_orig [.] findName 11.77% phonebook_orig [.] main 5.50% libc-2.23.so [.] _IO_fgets 4.78% libc-2.23.so [.] __memcpy_sse2 4.23% libc-2.23.so [.] _int_malloc 4.01% [kernel] [k] _raw_spin_lock 2.81% [unknown] [k] 0x00007f57f681f02e 2.55% libc-2.23.so [.] malloc 2.42% libc-2.23.so [.] __strcpy_sse2_unaligned 2.31% libc-2.23.so [.] _IO_getline_info 1.83% [kernel] [k] free_pcppages_bulk 1.78% [kernel] [k] __mod_zone_page_state 1.66% phonebook_orig [.] append 1.22% [kernel] [k] page_remove_rmap 1.12% [kernel] [k] __do_page_fault 1.09% [kernel] [k] handle_mm_fault 0.79% [kernel] [k] mem_cgroup_try_charge 0.61% [kernel] [k] free_pages_prepare 0.61% [kernel] [k] uncharge_list 0.61% [kernel] [k] unmap_page_range 0.58% phonebook_orig [.] strcasecmp@plt 0.56% [kernel] [k] perf_event_aux.part.57 0.54% [kernel] [k] clear_page_c_e 0.54% phonebook_orig [.] malloc@plt 0.54% libc-2.23.so [.] memchr 0.54% [kernel] [k] __rmqueue.isra.79 0.03% [kernel] [k] native_write_msr_safe ``` * 最後結果 * `findName()` 所占的時間: 21.91% , 執行時間: 0.004856 sec * `append()` 所占的時間: 1.66% , 執行時間: 0.036544 sec * `findName()`所占的時間比 `append()` 多,但是 `append()` 所花的執行時間卻比 `findName()` 長 ## 優化版本 >進度要加快摟[name=亮谷][color=#00d666] ### 版本一: 改變資料結構大小 * 參考 [示範用的 Phonebook 作業](https://hackmd.io/s/BJRMImUPg#), 發現`findName()`只有傳入 lastName , 所以在 phonebook_opt.h 裏的 `typedef struct __PHONE_BOOK_ENTRY` 刪除沒有用到的參數, 改變 `struct` 的大小。 ```shell=vim typedef struct __PHONE_BOOK_ENTRY { char lastName[MAX_LAST_NAME_SIZE]; /*1at version: Changing the size of the data structure*/ /*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; ``` * 執行時間 * entry 的大小從原本的 136bytes 減少到 現在的 24bytes * `append()` 的執行時間從 0.036544 sec 變成 0.035499 sec * `findName()` 的執行時間從 0.004856 sec 變成 0.001753 sec * `append()` 的執行時間減少不大, `findName()` 的執行時間減少很多,可是在真實情況不可能只有lastName, 所有效用不大 ``` size of entry : 24 bytes execution time of append() : 0.035499 sec execution time of findName() : 0.001753 sec ``` * cache-misses * 從原本的 85.73 % 降到 現在的 52.099 % * 因爲 entry 的大小改變 ``` Performance counter stats for './phonebook_opt' (100 runs): 8,6652 cache-misses # 52.099 % of all cache refs ( +- 1.09% ) 16,6322 cache-references ( +- 1.11% ) 2,4064,7152 instructions # 2.03 insns per cycle ( +- 0.02% ) 1,1844,9973 cycles ( +- 0.23% ) 0.034387209 seconds time elapsed ( +- 0.27% ) ``` * plot ![](https://i.imgur.com/mm1g0Qm.png) * 然後試着創一個新的結構,裏頭的結構就只有存 lastName 的 array 、 指向子結構(將原本的 entry 變成 sencondary_entry ) 的指標跟指向下一個的指標 ```clike typedef struct __PHONE_BOOK_ENTRY { char lastName[MAX_LAST_NAME_SIZE]; /*2nd version: use a smaller size of structure as the main searching structure*/ 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; } secondary_entry; typedef struct __LAST_NAME_ENTRY { char lastName[MAX_LAST_NAME_SIZE]; secondary_entry *detail; struct __LAST_NAME_ENTRY *pNext; } entry; ``` * 執行時間 * 執行時間變長了一點,可是這的架構在真實情況下是可行的 ``` size of entry : 32 bytes execution time of append() : 0.056086 sec execution time of findName() : 0.001877 sec ``` * cache-misses ``` Performance counter stats for './phonebook_opt' (100 runs): 13,1142 cache-misses # 44.918 % of all cache refs ( +- 0.51% ) 29,1957 cache-references ( +- 0.48% ) 2,4393,2710 instructions # 1.99 insns per cycle ( +- 0.02% ) 1,2235,4305 cycles ( +- 0.12% ) 0.035973557 seconds time elapsed ( +- 0.16% ) ``` * plot * ![](https://i.imgur.com/rbEDfZR.png) ## 版本二: (Hash) * 使用 BKDRHash 的方式來進行優化 * BKDRHash 的程式碼,參考 [各種字符串Hash函數比較](https://www.byvoid.com/zhs/blog/string-hash-compare) ```shell=vim unsigned int BKDRHash(char *str) { unsigned int seed = 131; //(2^n)-1, n>=5 unsigned int hash = 0; while (*str) hash = hash * seed + (*str++); return (hash & 0x7FFFFFFF) % TABLE_SIZE; } ``` * 執行時間: * `append()` 的執行時間增加了很多,可是 `findName()` 的執行時間卻減少了更多, 所以在整體的效能上是有增長的 ``` size of entry : 32 bytes execution time of append() : 0.099565 sec execution time of findName() : 0.000007 sec ``` * cache-misses * cache misses 是 24.444%,遠遠低於原始版本的 85.730% ``` Performance counter stats for './phonebook_opt_hash' (100 runs): 10,1023 cache-misses # 24.444 % of all cache refs ( +- 2.24% ) 41,3287 cache-references ( +- 0.34% ) 2,4139,0624 instructions # 1.54 insns per cycle ( +- 0.02% ) 1,5709,8979 cycles ( +- 0.35% ) 0.045364188 seconds time elapsed ( +- 0.38% ) ``` * plot ![](https://i.imgur.com/LI2LEkW.png) * 我嘗試用別的 hash function 來實做 * 我選的是DJBHash ```shell=vim unsigned int DJBHash(char *str) { unsigned int hash = 5381; while (*str) { hash += (hash << 5) + (*str++); } return (hash & 0x7FFFFFFF) % TABLE_SIZE; } ``` * 執行時間: * 發現`append()` 跟` findName()` 的執行時間比 BKDRHash 的來的短 ``` size of entry : 32 bytes execution time of append() : 0.042704 sec execution time of findName() : 0.000005 sec ``` * cache-misses * cache misses 也從 BKDRHash 的 24.44% 下降到 19.33% ``` Performance counter stats for './phonebook_opt_hash' (100 runs): 7,7377 cache-misses # 19.330 % of all cache refs ( +- 1.07% ) 40,0295 cache-references ( +- 0.34% ) 2,4100,9693 instructions # 1.63 insns per cycle ( +- 0.02% ) 1,4751,4201 cycles ( +- 0.12% ) 0.042510503 seconds time elapsed ( +- 0.13% ) ``` ### 問答 本例選用的 dataset (也就是輸入電話簿的姓名) 是否存在問題? * Q: 有代表性嗎?跟真實英文姓氏差異大嗎? A: 根據對 `./dictionary/words.txt` 的觀察, 裡面有很多不是非名詞, 甚至有一些是無意義的字串,所以代表性不大。 檔案裡頭的資料與真實英文姓氏差異不大,裡頭里的確存有很多與真實英文姓氏的字串, 只是也同時有很多無意義的字串。 * Q: 資料難道「數大就是美」嗎? A: 個人認為不是, 以 `./dictionary/words.txt` 來說, 資料量很大, 可是存在很多無意義的資料,這樣會造成降低搜尋的速度,導致效率下降。 數據大而有意義才美。 * Q: 如果我們要建立符合真實電話簿情境的輸入,該怎麼作? A: 尋找真實英文姓氏的字典