# 2020q3 Homework3 (dict) contributed by < `RainbowEye0486` > ## 視覺化 ternary search tree + bloom filter 的效能表現 當執行完 ``` $ ./test_common --bench CPY ``` 以及 ``` $ ./test_common --bench CPY ``` 後,我們可以發現檔案底下產生出一個 `bench_ref.txt` 檔案,這個檔案是由 `test_common.c` 定義: ```cpp=20 #define BENCH_TEST_FILE "bench_ref.txt" ``` 並且呼叫 `bench.c` 中的函式產生 ```cpp=103 if (argc == 3 && strcmp(argv[1], "--bench") == 0) { int stat = bench_test(root, BENCH_TEST_FILE, LMAX); tst_free(root); free(pool); return stat; } ``` 接下來,我們要比較 `CPY` 和 `REF` 之間的差異,發現其實整體看下來,效能以 `CPY` 較佳,而且`CPY` 的方式整體來說穩定的多,大部分數值都落在0.7以下,原因老師也有提到,在 `tst.c` 中可以發現在 insert 的時候,字串會直接 `malloc` ,記錄下來,因此搜尋的時候有很大的機會會在 `catch` 裡面找到,反而是 `REF` 的機制,將地名放在 `memory pool` 裡面,所以每次尋找的時候都有可能還需要去記憶體裡面 load `memory pool` 的某個部份進來,所以效能表現較差,這個部份和講義上面的一樣。 至於為什麼在 `REF` 機制中有些點出現比周圍大很多的值呢?(這邊為了版面一致所以 y 軸取一樣的級距,但是 `REF` 機制出現零碎的極大值情形較 `CPY` 機制頻繁)根據上面的 `cache miss rate` 去推測,很有可能就是當 `cache miss` 的時候,必須去 memory 裡面抓取資料所增加的 `latency` ![](https://i.imgur.com/Gl73SOK.png) - `CPY` 對於 prefix search 機制效能 ![](https://i.imgur.com/Ny0bLAy.png) - `REF` 對於 prefix search 機制效能 ```cpp=239 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; } } ``` 分析 `tst.c` 關於 insert 的敘述,當 `CPY` 時會 malloc 保存字串,增加額外的記憶體空間, 接著,比較完 prefix 搜尋之後,我們設計實驗觀察 `REF` `CPY` 兩種機制下,加上 `bloom filter` 前後,幾種字串輸入的效能分析。 **實驗一:字典內部隨機選取** 這個實驗是將九萬筆城市資料進行隨機取樣,然後進行搜尋,得到的結果如下圖所示,對於有加 `bloom filter` 與沒加 `bloom filter` 的情況,有加 `bloom filter` 低於0.002秒的圖形底層其實比較稀疏一點,代表平均起來搜尋結果高於0.002秒的點較常出現,整體搜尋時間來說,沒有 `bloom filter` 的搜尋會稍微快一些(圖片上面較不明顯),原因是因為有加 `bloom filter` 的情形在搜尋開始之前的時候會先重新將字串透過 `hash function` 轉換一次,進行 `hash table` 對 `hash key` 的確認後才會進入真正的 `tst` 搜尋,在這個情形中,由於我們所有的輸入字串都是從字典內部取得,所以比較均為成功,多出的 hash mapping 計算反而增加效能的負擔。 | \ | 有 bloom filter | 沒有 bloom filter | | --- | --- | --- | | **CPY** |![](https://i.imgur.com/9VPG2Dl.png)|![](https://i.imgur.com/HZJSo5q.png)| | **REF** |![](https://i.imgur.com/lDNLj9g.png)| ![](https://i.imgur.com/cEqmxUF.png)| 經過比較之後,我們發現對於搜尋來說,其實 `REF` 機制和 `CPY` 機制並沒有相差很大,推測其中一個原因是因為平均秒數過小,搜尋時間受環境或是因素影響較大,所以 `cache miss` 造成的 `latency` 影響沒有顯現出來。 **實驗二:隨機字串(固定開頭大寫之後小寫)** 實驗二內容是隨機給定一個字串,只有開頭是大寫,其他都是小寫,字串的長度為6個字元,進行搜尋之後,呈現下圖的結果。實驗的預期原本猜測是有 `bloom filter` 的情形搜尋時間會大幅降低,因為對於一個隨機字串,透過 `hash function` 轉換之後應該很快就可以知道此字串是否屬於 `cities.txt` ,而且由於我們的字串是隨機的,所以應該不在`cities.txt` 的機率比較高才對(猴子打字機原理),但是結果出來效能並沒有提昇,反而朝向一個奇怪的方式分佈: | \ | 有 bloom filter | 沒有 bloom filter | | --- | --- | --- | | **CPY** |![](https://i.imgur.com/dKfOovJ.png)|![](https://i.imgur.com/iIMc4B9.png)| | **REF** |![](https://i.imgur.com/P3fIv0T.png)|![](https://i.imgur.com/8vdd1Vf.png)| 思考一下這樣的結果,發現一個有趣的現象,由於字串的隨機性,當我們從 `tst` 的 root 開始比對下來時,也有很高的機率直接 return 不存在。 :::success 舉例來說: 搜尋 `Be` 當作 prefix 得到 2609 個可能的城市 搜尋 `Bl` 當作 prefix 得到 376 個可能的城市 搜尋 `Bg` 、 `Bk` 、 `Bt` 當作 prefix 均得到 No result ::: 這是根據文化、語法、發音等等原因,所以開頭高度集中於某些特定的字首,在第二個字元開始分散就極度不均等的情況下,到第二個字元回傳不存在的機率因此大幅上升,同時也降低了整體的搜尋時間。 :::info TODO:研究為何在有 `bloom filter` 的情形搜尋時間分佈會呈現兩個區段分佈 ::: ## 以 perf 分析 TST 程式碼的執行時期行為並且充分解讀 用 perf 分析,輸入 ``` $ sudo perf stat ./test_common --bench CPY ``` ``` $ sudo perf stat ./test_common --bench REF ``` 得到以下結果: ``` CPY mechanism ternary_tree, loaded 206849 words in 0.174244 sec Performance counter stats for './test_common --bench CPY': 16,104.35 msec task-clock # 0.986 CPUs utilized 67 context-switches # 0.004 K/sec 0 cpu-migrations # 0.000 K/sec 7,348 page-faults # 0.456 K/sec 51,966,296,466 cycles # 3.227 GHz 50,435,658,306 instructions # 0.97 insn per cycle 7,538,992,738 branches # 468.134 M/sec 224,385,189 branch-misses # 2.98% of all branches 16.329711541 seconds time elapsed 16.025359000 seconds user 0.079986000 seconds sys ``` ``` REF mechanism ternary_tree, loaded 206849 words in 0.120071 sec Performance counter stats for './test_common --bench REF': 18,852.90 msec task-clock # 0.999 CPUs utilized 79 context-switches # 0.004 K/sec 1 cpu-migrations # 0.000 K/sec 7,170 page-faults # 0.380 K/sec 55,456,300,927 cycles # 2.942 GHz 50,414,057,902 instructions # 0.91 insn per cycle 7,534,475,849 branches # 399.645 M/sec 224,267,391 branch-misses # 2.98% of all branches 18.867488778 seconds time elapsed 18.821164000 seconds user 0.032001000 seconds sys ``` 觀察到,載入時間 `REF` 效能較高,因為不需要額外的 `malloc` 時間。 讀取 `cache` 內部,得到: ``` CPY mechanism ternary_tree, loaded 206849 words in 0.171644 sec Performance counter stats for './test_common --bench CPY': 1,677,730,516 cache-misses # 59.616 % of all cache refs 2,814,241,920 cache-references 50,431,936,426 instructions # 1.00 insn per cycle 50,604,876,868 cycles 14.978900109 seconds time elapsed 14.887244000 seconds user 0.044009000 seconds sys ``` ``` REF mechanism ternary_tree, loaded 206849 words in 0.166995 sec Performance counter stats for './test_common --bench REF': 1,844,243,164 cache-misses # 59.582 % of all cache refs 3,095,309,932 cache-references 50,410,105,265 instructions # 0.92 insn per cycle 55,018,747,093 cycles 18.346063654 seconds time elapsed 18.244198000 seconds user 0.060013000 seconds sys ``` 相較之下, `REF` 的 `cache miss` 較高,符合第一段討論的結果。