# 2020q3 Homework3 (dict) contributed by < `shauming1020` > ###### tags: `homework` ## 效能表現與分析 > 視覺化 ternary search tree + bloom filter 的效能表現並分析 > 1. 要考慮到 prefix search 的特性還有詞彙的涵蓋率 > 2. 限用 gnuplot 來製圖 - 測量在不同情境下 ```res = tst_search(root, word);``` 的執行時間 - Hash 參數 ```cpp #define TableSize 50000000 /* size of bloom filter */ #define HashNumber 5000000 /* number of hash functions */ ``` - 從 cities 中隨機取 5000 筆當作測資 ### 情境 1: 搜尋字串全部在字典內 > 以 cities.txt 作為輸入搜尋 ![](https://i.imgur.com/BV9lW5p.png) ![](https://i.imgur.com/SGYYpZL.png) 由於輸入都是 cities 中的字串,因此大多情況下沒有採用 bloom filter 的搜尋時間會來的快,省去額外執行 bloom filter 的時間 ### 情境 2: 搜尋字串在第一個字元即可判斷不在字典內 > e.g. word[0] = '$'; ![](https://i.imgur.com/m1GabyV.png) ![](https://i.imgur.com/rm82XqM.png) 由於第一個字在字典中沒有出現過,因此至多比較常數次數(英文字母的大小寫個數)即可知道沒有這個字串,可以很明顯看到採用 bloom filter 的執行時間會比沒有的還來得久 ### 情境 3: 搜尋字串全部在字典內的 prefix > e.g. Tokyo 在 cities 內,取其 prefix 'Toky' 作為輸入 ![](https://i.imgur.com/iNZmWrf.png) ![](https://i.imgur.com/BSKYGJN.png) 以 prefix 作為輸入,TST 走訪到 leaf node 的上一層才知 miss,因此在大多情況下,無論是否有 bloom filter,執行時間都會比情境 1 和 2 來得多 ### 情境 4: 搜尋隨機字串 > 隨機生成長度 1 - 256 的字串 ![](https://i.imgur.com/MyDbdMg.png) ![](https://i.imgur.com/KKUR5yB.png) ## 以 perf 分析 TST 程式碼的執行時期行為並且充分解讀 - Search ``` bash= echo 3 | sudo tee /proc/sys/vm/drop_caches; sudo perf stat --repeat 500 \ -e cache-misses,cache-references,instructions,cycles \ ./test_common --bloom CPY "" sudo perf stat --repeat 500 \ -e cache-misses,cache-references,instructions,cycles \ ./test_common --bloom REF "" ``` > $./test_common --bloom CPY search-a-word > 1. bloom or bloom-wo,是否啟動 bloom filter > 2. CPY or REF,模式的選擇 > 3. search-a-word,在 TST 中搜尋一個 word,當 word 的長度為 0 時 (word=""),則從 cities 中隨機取 5000 筆資料用來分析上述四種情況 - 分析結果 ```log Performance counter stats for './test_common --bloom CPY ' (500 runs): 3,447,745 cache-misses # 31.710 % of all cache refs ( +- 0.64% ) 10,872,620 cache-references ( +- 0.17% ) 721,588,119 instructions # 1.22 insn per cycle ( +- 0.01% ) 591,450,395 cycles ( +- 0.16% ) 0.126787 +- 0.000248 seconds time elapsed ( +- 0.20% ) ``` ```log Performance counter stats for './test_common --bloom REF ' (500 runs): 2,976,359 cache-misses # 27.486 % of all cache refs ( +- 0.59% ) 10,828,747 cache-references ( +- 0.13% ) 697,580,790 instructions # 1.22 insn per cycle ( +- 0.00% ) 574,036,069 cycles ( +- 0.12% ) 0.122579 +- 0.000206 seconds time elapsed ( +- 0.17% ) ``` - Report ```log $perf record -e cache-misses ./test_common --bloom CPY "" && perf report 37.12% test_common test_common [.] tst_free_all 7.76% test_common test_common [.] tst_search 5.18% test_common test_common [.] tst_ins_del ``` ```log $perf record -e cache-misses ./test_common --bloom REF "" && perf report 36.60% test_common test_common [.] tst_free 8.92% test_common test_common [.] tst_search 5.97% test_common test_common [.] tst_ins_del ``` - Prefix Search ```bash= echo 3 | sudo tee /proc/sys/vm/drop_caches; sudo perf stat --repeat 100 \ -e cache-misses,cache-references,instructions,cycles \ ./test_common --bench CPY "s Tai" sudo perf stat --repeat 100 \ -e cache-misses,cache-references,instructions,cycles \ ./test_common --bench REF "s Tai" ``` - 分析結果 ```log Performance counter stats for './test_common --bench CPY s Tai' (500 runs): 2,721,171 cache-misses # 27.798 % of all cache refs ( +- 0.57% ) 9,789,154 cache-references ( +- 0.17% ) 584,808,232 instructions # 1.23 insn per cycle ( +- 0.01% ) 476,011,493 cycles ( +- 0.11% ) 0.102034 +- 0.000192 seconds time elapsed ( +- 0.19% ) ``` ```log Performance counter stats for './test_common --bench REF s Tai' (500 runs): 2,227,871 cache-misses # 23.064 % of all cache refs ( +- 0.30% ) 9,659,418 cache-references ( +- 0.14% ) 550,516,998 instructions # 1.20 insn per cycle ( +- 0.00% ) 458,495,558 cycles ( +- 0.05% ) 0.0976760 +- 0.0000603 seconds time elapsed ( +- 0.06% ) ``` - 從結果中發現 | 比較 | CPY | REF | | -------- | -------- | -------- | | cache-misses| 多 | 少 | | instructions | 多 | 少 | | running-time | 較慢 | 較快 | ## CPY & REF > 考慮到 CPY 和 REF 這兩種實作機制,思考效能落差的緣故,並且提出後續效能改善機制 > 1. 充分量化並以現代處理器架構的行為解讀 > 2. 如何兼顧執行時間和空間運用效率? - REF 機制會配置 memory pool 來存放資料,而 CPY 機制則採用變數 word 作為 buffer 暫存一筆資料 ```c= test_common.c /* memory pool */ pool = (char *) malloc(poolsize * sizeof(char)); Top = pool; ``` - CPY 機制當插入節點時。走訪到 string 尾端會額外 malloc 與 s 相同長度的空間,並讓 curr->eqkid 指向,而 REF 機制則是直接讓 curr->eqkid 指向 Top (即 pool 內的空間) ```c= tst_ins_del /* Place nodes until end of the string, at end of stign allocate * space for data, copy data as final eqkid, and return. */ if (*p++ == 0) { if (cpy) { /* allocate storage for 's' */ const char *eqdata = strdup(s); if (!eqdata) return NULL; curr->eqkid = (tst_node *) eqdata; return (void *) eqdata; } else { /* save pointer to 's' (allocated elsewhere) */ curr->eqkid = (tst_node *) s; return (void *) s; } } ``` ### 思考效能落差的緣故 - CPY 機制所需要執行的指令較多,例如在 insert 時 ```const char *eqdata = strdup(s);```及 tst_free_all ```free(p->eqkid);``` 等等 - 參考[隱藏在字典裡的數學](https://hackmd.io/@sysprog/BkE3uSvdN#%E4%BD%BF%E7%94%A8-perf-%E5%88%86%E6%9E%90%E7%A8%8B%E5%BC%8F%E6%95%88%E8%83%BD),預期 REF cache-misses 次數會較多,但從我的實驗結果來看和參考來源不一致,這裡沒有頭緒... - 重新省思自己的實驗設計流程 ## 研讀 Xor Filters: Faster and Smaller Than Bloom and Cuckoo Filters 論文並參閱對應程式碼實作 xor_singleheader > 1. 引入上述 dict 程式碼中,需要比較原有的 bloom filter 和 xor filter 在錯誤率及記憶體開銷 > 2. 善用 Valgrind 和 Massif,使用方式參見 I01: lab0 ## 如何縮減頻繁 malloc 帶來的效能衝擊 > 1. 儲存字串用的 memory pool 在 REF 版本中已實作,但在 tst_ins_del() 當中頻繁以 calloc() 配置節點空間造成的效能衝擊,應許以改進。 > 2. REF 版本中的 pool 只會一直增添新的資料,並沒有從中移除資料的操作發生,因此可以用更進階的手段來管理 > 3. tst 則會有移除節點的操作,我們無法確定要被刪除的節點他使用的記憶體落在 memory pool 的哪個地方,如何管理 memory pool, 使之不會有記憶體碎片產生,是這個問題的關鍵。 ## 在現有的開放原始碼專案中,找出使用 bloom filter 的專案,並解析其導入的考量