2017q1 Homework1 (phonebook)
contributed by <ryanwang522
>
Reviewed by csielee
- 關於 strcasecmp() 的 byte-by-byte comparsion 就是會將傳入的字串 s1 s2 將給個字元個別比較(字元大小是一個 byte ),並紀錄相差的數值,最後將數值回傳。因此回傳值有大於0、等於0、小於0,三種狀況
- 在BST實作當中,並沒有把建立BST的時間計算進 append() 的時間,這樣會造成BST好像比較有效率的錯覺。
- 在BST中,可以加入能夠動態增加資料。因為目前phonebook是都會先新增然後查詢,但實際狀況是會動態的查詢跟新增
- 儘量不要修改 main.c 使用 findname 或 append 的函數方法,可以利用 OOP 的觀念,將不同版本的操作隱藏在函數裡
開發環境
- OS: ubuntu 16.04 LTS
- L1d cache: 32K
- L1i cache: 32K
- L2 cache: 256K
- L3 cache: 3072K
- Architecture:x86_64
- CPU op-mode(s): 32-bit, 64-bit
- Byte Order: Little Endian
- CPU(s): 4
- Model name: Intel® Core™ i5-4200H CPU @ 2.80GHz
原始版本
-
初始設定並初步執行
- 一開始為了了解 Git 的指令操作花了不少時間,但還是只弄懂了一點點皮毛,背後詳細的原理有待之後再來深入研究。
- 大致瀏覽過相關資料後,覺得還是實地動手做做看吧!
-
清空 cache
看到報表後覺得怪怪的,miss rate 和其他人好像有些差異Ryan Wang
- 接著來試試新鮮的 gnuplot
$ sudo make plot
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
初步構思
- 想法:
- 觀察
main.c
的程式碼,不管是append()
或findName()
在原本龐大的 struct 裡面只有用到 lastName
,縮小結構內容應該對於效能改善助益不少。
- 使用 hash function 增加查找效率。
實際嘗試
縮小 Structure 內容
- 優化前版本的 Structure size = 136 bytes ,而我的 layer1 cache 也只有 32k bytes 頂多也只能放 32 * 1024 / 136 = 240.94 筆資料,在查找將近 35 萬筆資料時,無可避免的會發生大量 cache-miss,縮小體積後的 struct size = 32 bytes,能放 1024 筆資料,應該能有效減少 cache-miss。
- 更改 structure 內容以縮小體積
- 將原本的詳細資料獨立出來,透過指標存取。
- 避免影響程式效能,先不
malloc()
給 detail 所指向的內容,往後需要用到內容時再分配空間給它就好。
$ sudo make plot

發現 Cache-misses 從原本的 85.5% 降至 40.11%!
表示原本的 structure 放置太多查找不需要的內容對 cache-misses 的 overhead 影響不小。
使用 Hash function
記得以前在上 OS 課程時有學過 hash 的基本概念,主要是由建立適當大小的 hash table 後,接著再由雜湊函數得到的雜湊值對照 table 去增加查找效率。而需要注意的地方有以下幾點。
- 如何決定 Table size
- 這裡我是根據電腦的 cache 所能存放的大小決定給 1024,避免查表造成過多的 cache-misses。
- 使用何種 Hash function
- 看了提供的資料以及一些先前同學的共筆,djb2 這個函數的均勻度似乎不錯,且改善效能的程度也算顯著,不過後來看到作業區提供的 BKDR ,就想說來試試吧!
之所以會將原來的雜湊值對 table size 取餘數是因為 size 大小的限制,而當 Collision(碰撞)發生,我採取的方法是 Chaining,直接將重複的資料串在一起,示意圖如下。

- 接著我對
main.c
做了相對應的修改,原本的 singly linked list 就保留著沒使用。
發現好像直接修改 main.c
在執行 make
時會因為 #ifdef 的問題導致有原始版本的編譯錯誤,不過就先測試看看,以後再來修改Ryan Wang
-
發現 append()
的執行時間有些許的增加
- 想了想覺得是因為建立 hash table 所造成的 overhead
- 不過看了
findName()
的時間大幅下降,證明了使用 hash function 雖然會犧牲一點append()
的效能,但著實能改善效率。
-
Cache misses 從 40% 降為 26.91% ,因為適當的 table size 使得不僅查找效率提昇,miss rate 也順帶降低。
-
參考 Conditional Compilation,解決先前的 orig 版本編譯問題。
- 現在想想應該對
append()
以及findName()
進行修改的,直接改main.c
似乎有點不明智QQ
-
$ sudo make plot
- 這時發現畫出來的圖跟之前一樣,研究了一下發現
output.txt
還是舊的,執行$ make clean
後就可以正常使用 plot
了。

-
因為先前 commit 的內容被 jserv 教授糾正了不少錯誤的寫法,所以花了一點時間研究 How to write a git commit message,這裡稍微整理一下。
-
Commit message 大致分為:
Subject line
- 長度限制為 50 個 Characters,開頭大寫且不需要加句點。
- 內容需簡短扼要,如果真的無法在長度限制內撰寫代表你應該分成多次進行 commit。
- 簡單的檢查方法就是將你的 Subject line 代入「 If applied, this commit will your subject line here 」
Message body
- 長度限制在 72 個 Characters 左右。
- 顧名思義,body 就是讓你詳細解釋此次 commit 的原因以及內容,可以適時的進行分段。
-
最後看到一句不錯的話紀錄一下
- The future maintainer that thanks you may be yourself!
使用 Binary Search Tree
- 在想要怎麼實做的時候因為不太懂「字串」要依照什麼規則放入 BST 裡面,閱讀了許多資料後發現是利用
strcasecmp()
的回傳值作為要放置在左子樹還是右子樹的依據。
這裡還是不太懂 byte-by-byte comparsion 的實際運作方法,回頭再來研究。 Ryan Wang
- 接著修改
findName()
,利用循序的方式找出目標。
- 與先前 hash function 版本以及原始版本進行比較
可見 findName()
的時間又比 hash function 版本有些微的減少。
- 更改了一下
runtime.gp

原本 Linked list 的查找時間複雜度為 O(n) ,將資料結構調整成 BST 後時間複雜度為 O(logn),由圖表也可得知查找效率也大幅改善。
針對 Cache line 調整 BST 結構
問題探討
- 本例選用的 dataset (也就是輸入電話簿的姓名) 是否存在問題?
- 輸入電話簿的姓名可能會有相同姓名的情況出現,需要進而比對其他資料的,可以選用每個人獨立的 ID 作為電話簿的 dataset ,避免多餘的查詢比對。
- 有代表性嗎?跟真實英文姓氏差異大嗎?
- 覺得跟真實情況有些微的出入,真實英文姓氏為
firstname
+ lastName
,而本例只有 firstname
。
- 資料難道「數大就是美」嗎?
- 大量的資料要配合適合的資料結構去儲存,以及有效率的演算法為基礎的查詢方式,單純大量且沒有經過縝密規劃的大量資料反而會成會負擔。
- 如果我們要建立符合真實電話簿情境的輸入,該怎麼作?
- 利用現有的英文姓名去做隨機配對,避免字典檔的一些不符合姓名規則的輸入資料。
參考資料