# 2020q3 Homework3 (dict) contributed by < `blueskyson` > ###### tags: `linux2020` ## 目錄 [TOC] ## 視覺化 ternary search tree + bloom filter 的效能表現並分析 ### ternary search tree 效能 首先執行 `$ ./test_common --bench CPY` ,輸出 bench_ref.txt , 再使用 gnuplot 畫出分佈圖, gnuplot 指令如下: ``` set xlabel 'city-string index' set ylabel 'time(msec)' set title 'tst\_search\_prefix time costs' plot [:260000][:]'bench_ref.txt' using 1:2 with points ``` 輸出圖表如下: ![](https://i.imgur.com/k43qcm6.png) 此圖的 y 座標為 tst.c 中 `tst_search_prefix()` 每次搜尋補字所消耗的時間,單位為微秒。 x 座標則是代表 `tst_search_prefix()` 讀入第 x 個字串,每個字串是 cities.txt 的城市或國家名中取前 3 個字元,使用 3 個字元當作 prefix 搜尋。總共從 cities.txt 讀取大約 260000 個字串。 再來執行 `$ ./test_common --bench REF` ,一樣會輸出 bench_ref.txt ,分佈圖為: ![](https://i.imgur.com/7Db9Y1D.png) 比較 CPY 和 REF 機制,發現 CPY 消耗的時間大部份都在 0.6 秒以下, REF 消耗的時間大部分在 0.7 至 0.8 秒以下。比較搜尋時間, CPY 的效能比 REF 好。 ### bloom filter 效能 由於 `bench.c` 沒有內建的 bloom filter 效能分析,我在 `bench.c` 加入類似 `bench_test()` 的函式,它會從 cities.txt 中讀取地名,並且去除 `,` 和 `\n` 字元,然後呼叫 `bloom_test()` 檢查其是否在, bloom filter 中,以下展開為程式碼: :::spoiler bench_test2() ```c= int bench_test2(bloom_t bloom, const tst_node *root, char *out_file, const int max) { char word[WORDMAX] = ""; char **sgl; FILE *fp = fopen(out_file, "w"); FILE *dict = fopen(DICT_FILE, "r"); int idx = 0; double t1, t2; if (!fp || !dict) { if (fp) { fprintf(stderr, "error: file open failed in '%s'.\n", DICT_FILE); fclose(fp); } if (dict) { fprintf(stderr, "error: file open failed in '%s'.\n", out_file); fclose(dict); } return 1; } sgl = (char **) malloc(sizeof(char *) * max); while (fscanf(dict, "%s", word) != EOF) { int lastchar = strlen(word) - 1; if (word[lastchar] == ',' || word[lastchar] == '\n') word[lastchar] = '\0'; t1 = tvgetf(); bloom_test(bloom, word); t2 = tvgetf(); fprintf(fp, "%d %f msec\n", idx, (t2 - t1) * 1000); idx++; } free(sgl); fclose(fp); fclose(dict); return 0; } ``` ::: <br> 然後我在 `test_common.c` 中加入: ```c= if (argc == 3 && strcmp(argv[1], "--bench2") == 0) { int stat = bench_test2(bloom, root, "bench_ref2.txt", LMAX); tst_free(root); free(pool); return stat; } ``` 做完這些更動之後,執行 `$ ./test_common --bench2 CPY` 即可輸出每次執行 bloom_test 所消耗的時間 bench_ref2.txt ,畫出分佈圖: ![](https://i.imgur.com/DlpfwxE.png) 執行 `$ ./test_common --bench2 REF` ![](https://i.imgur.com/GQTlq7l.png) 可以發現不論是 CPY 或 REF 執行 bloom_test 的速度都非常快,幾乎在 0.002 微秒以下。 ## 以 perf 分析 TST 程式碼的執行時期行為並且充分解讀 首先執行 5 次 perf ,分析 `./test_common --bench CPY` 的行為: ``` $ perf stat -e branches,branch-misses,cache-misses,cycles,instructions,context-switches ./test_common --bench CPY CPY mechanism ternary_tree, loaded 206849 words in 0.142208 sec Performance counter stats for './test_common --bench CPY': 7,545,228,775 branches 236,997,577 branch-misses # 3.14% of all branches 1,589,264,930 cache-misses 51,561,737,832 cycles 52,179,870,840 instructions # 1.01 insn per cycle 57 context-switches 16.466583643 seconds time elapsed 16.421854000 seconds user 0.036004000 seconds sys ``` 執行 5 次 perf ,分析 `./test_common --bench REF` 的行為: ``` $ perf stat -e branches,branch-misses,cache-misses,cycles,instructions,context-switches ./test_common --bench REF REF mechanism ternary_tree, loaded 206849 words in 0.154480 sec Performance counter stats for './test_common --bench REF': 7,541,064,992 branches 237,372,869 branch-misses # 3.15% of all branches 1,682,579,144 cache-misses 57,721,488,007 cycles 52,159,621,475 instructions # 0.90 insn per cycle 67 context-switches 18.673287298 seconds time elapsed 18.639914000 seconds user 0.031999000 seconds sys ``` 分析 perf 的資訊,發現 CPY 和 REF 機制耗費的 `instruction` 數量差不多,都在 520 億個指令左右,然而 CPY 卻耗費較少執行時間。 CPY 可以耗費較少時間是因為 `tst` 每次建立新的 `leaf` 時,會在 heap 分配新的空間儲存字串,因此在寫入以及走訪 tst 時,有比較大的機率存取到相鄰的記憶體位置,進而降低 `cache misses` 。從 perf 的數據也可發現 CPY 的 `cache misses` 為 1,589,264,930 , REF 的 `cache misses` 為 1,682,579,144 ,符合前述推論。 ## 考慮到 CPY 和 REF 這兩種實作機制,思考效能落差的緣故,並且提出後續效能改善機制 同前面所述,效能落差顯現於 `cashe misses` 。 改善機制: 目前還沒有想法 ## 研讀 [Xor Filters: Faster and Smaller Than Bloom and Cuckoo Filters](https://arxiv.org/abs/1912.08258) 論文並參閱對應程式碼實作 xor_singleheader,引入上述 dict 程式碼中,需要比較原有的 bloom filter 和 xor filter 在錯誤率及記憶體開銷