# 2018q3 Homework3 (dict) ###### tags: `C語言` contributed by < `asd757817` > ``` 實驗環境 作業系統: Ubuntu 16.04 x64 kernel: 4.15.0-34-generic gcc 版本: gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10) CPU:Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz ``` ## 原專案功能介紹&測試 ### 原專案提供的 --bench 搜尋效率檢查 原專案預設的 --bench 這個參數協助測試,輸入: ```shell $ make && ./test_cpy --bench ``` 這一份測試會逐行讀取 cities.txt 的內容,以","分隔字串並以輸入字串的前三個字元進行 prefix search,紀錄 prefix search 所耗費的時間最後輸出到 bench_cpy.txt 內。 - tst_search_prefix 函式: 比對傳入的字串(傳入的字串是要搜尋字串的 prefix,如: china 會傳 chi 給 tst_search_prefix),若能找到 chi 分別對應的節點則以 i 的結點作為 root,呼叫 tst_suggest。 - tst_suggest 函式: 以傳入的節點做為 root,走訪之下的所有分支並紀錄到 array 中。 參考< Jyun-Neng >同學的共筆得知可以使用原專案的 script 作圖 ```shell $ gnuplot scripts/runtime3.gp $ eog runtime3.png ``` ![](https://i.imgur.com/4n6VpIx.png) 圖中顯示時間單位為"秒",但一般而言搜尋的時間不應該耗費上百秒,因此查看程式碼 bench.c ```C double tvgetf() { struct timespec ts; double sec; clock_gettime(CLOCK_REALTIME, &ts); sec = ts.tv_nsec; sec /= 1e9; sec += ts.tv_sec; return sec; } ``` bench_cpy.txt 的內容是根據下列程式 ```C t1 = tvgetf(); t2 = tvgetf(); fprintf(fp, "%d %f sec\n", idx, (t2 - t1) * 1000000); ``` t1、t2 透過 tvgetf() 獲得數值,單位為"秒",故 ( t1 - t2 ) 的單位也為秒,乘以 \*1000000 得到的應該是"奈秒",修改 runtime3.gp 的 y 軸單位;此外原先圖形 x 軸只顯示到 12500,但輸入的資料約有25000筆,順手修改做圖時 x 軸的範圍。 修改後輸出圖形如下: ![](https://i.imgur.com/bZfCTFR.png) ## FIXED ### 新增 ./test_ref --bench 測試程式 以 ./test_cpy --bench 能產生 bench_cpy.txt 的測試文件,但執行 ./test_ref 卻會出現錯誤,查看 test_ref.c 發現並沒有加入 --bench 功能。 ``` $ ./test_ref --bench CC test_ref.o LD test_ref ternary_tree, loaded 259112 words in 0.264681 sec Commands: a add word to the tree f find word in tree s search words matching prefix d delete word from the tree q quit, freeing all data [1] 8203 segmentation fault (core dumped) ./test_ref --bench ``` 參考 test_cpy.c 程式碼,修改 test_ref.c 的內容,在載入字典檔後判斷是否輸入 --bench ```C ...以上略... t2 = tvgetf(); fclose(fp); printf("ternary_tree, loaded %d words in %.6f sec\n", idx, t2 - t1); if (argc==2 && strcmp(argv[1],"--bench")==0){ int stat = bench_test(root,BENCH_TEST_FILE,LMAX); tst_free(root); return stat; } ...以下略... ``` 檢查功能是否正常 ```shell $ make && ./test_ref --bench CC test_ref.o LD test_ref ternary_tree, loaded 259112 words in 0.262914 sec $ cat bench_ref.txt 0 236.034393 nsec 1 613.689423 nsec 2 281.333923 nsec 3 41.484833 nsec 4 248.670578 nsec 5 165.700912 nsec 6 82.969666 nsec 7 310.182571 nsec 8 90.122223 nsec 9 130.414963 nsec 10 95.605850 nsec 11 219.106674 nsec 12 438.928604 nsec 13 436.544418 nsec 14 12.397766 nsec 15 133.514404 nsec 16 176.191330 nsec 17 61.750412 nsec 18 919.580460 nsec 19 36.001205 nsec 20 545.978546 nsec 21 99.658966 nsec 22 27.179718 nsec 23 630.617142 nsec 24 263.929367 nsec 25 228.881836 nsec 26 271.558762 nsec 27 72.002411 nsec 28 294.685364 nsec 29 331.401825 nsec 30 382.900238 nsec 31 165.462494 nsec 32 162.601471 nsec 33 63.657761 nsec 34 214.576721 nsec 35 59.843063 nsec 36 46.253204 nsec ...... ``` 在 Makefile 新增 plot,繪製 cpy、ref 輸入 --bench 的搜尋時間圖 ``` plot: bench_cpy.txt test_cpy test_ref ./test_cpy --bench ./test_ref --bench gnuplot scripts/runtime3.gp eog runtime3.png ``` 修改 runtime3.gp,同時輸出 cpy、ref runtime 的時間圖 ``` reset set xlabel 'prefix' set ylabel 'time(nsec)' set title 'perfomance comparison' set term png enhanced font 'Verdana,10' set output 'runtime3.png' set format x "%10.0f" set xtic 1200 set xtics rotate by 45 right plot [:25000][:1000]'bench_cpy.txt' with points,'bench_ref.txt' with points \ ``` 測試結果 ```shell $ make plot ./test_cpy --bench ternary_tree, loaded 259112 words in 0.209411 sec ./test_ref --bench ternary_tree, loaded 259112 words in 0.267564 sec gnuplot scripts/runtime3.gp eog runtime3.png ``` ![](https://i.imgur.com/4Hki875.png) 從這樣的圖形無法進行比較,因此將數據以搜尋時間為 x 軸、x 軸區間50奈秒、y 軸統計次數重新做圖。 新增 test.gp ``` clear reset set xlabel 'search time(nsec)' set ylabel 'count' set title 'perfomance comparison' set term png enhanced font 'Verdana,10' set output 'test.png' stats 'bench_cpy.txt' using 2 name 'cpy' stats 'bench_ref.txt' using 2 name 'ref' set arrow 1 from cpy_mean,graph 0.5 to cpy_mean,graph 0 set label 1 at cpy_mean, graph 0.5 "mean cpy" center offset 3,1 set arrow 2 from ref_mean,graph 0.5 to ref_mean,graph 0 set label 2 at ref_mean, graph 0.5 "mean ref" center offset 4,-1 set boxwidth 1.0 absolute #set style fill solid 1.0 noborder bin_width = 1; bin_number(x) = floor(x/bin_width) rounded(x) = bin_width * ( bin_number(x) + 0.5 ) plot [0:2][0:]'bench_cpy.txt' using (rounded($2)):(1) smooth freq with boxes title 'cpy',\ 'bench_ref.txt' using (rounded($2)):(1) smooth freq with boxes title 'ref',\ ``` ```shell $ gnuplot scripts/test.gp && eog test.png ``` 修改 Makef ``` plot: bench_cpy.txt test_cpy test_ref ./test_cpy --bench ./test_ref --bench gnuplot scripts/test.gp eog test.png ``` ![](https://i.imgur.com/wIrfXRf.png) CPY、REF 兩者的差別在於 REF 引入 bloom filter,在搜尋之前先透過 bloom filter 預測輸入的字串是否在字典中,預測在字典中才進入 tree 中搜尋。 從圖中發現在當前的測試樣本 ref 搜尋的時間會略大於 cpy,這是因為搜尋的樣本使用的也是 cities.txt,全部搜尋的字串都在字典檔裡,ref 因為多了 bloom filter 判斷才開始搜索,會多耗費 bloom filter 的判斷時間,後續搜尋的時間與 cpy 一致,因此平均的搜尋時間會略大於 cpy。 ### 修改測試的內容 原測試程式以 tst_search_prefix 函式進行搜尋,搜尋只管前三個字元,不管實際輸入的字串是否真的在字典檔內,為了使 bloom filter 效果更加明顯,改以 tst_search Bloom filter 最理想的情境為:輸入字串的長度為 n,前 n-1 個字元都能夠在 tree 中找到對應的節點,直到最後一個字元才發現輸入字串不在 tree 裡,而透過 bloom filter 可以預設出此字串不在 tree 中 當輸入的搜尋字串不在字典檔內的比例夠高時,bloom filter 的效用還能夠體現,因此修改複製一份 cities.txt,將裡面的部份文字以其他文字取代,使得輸入字串與原本有差異,比較 bloom filter 效益。 **bench.c 修改** - 引用 bloom.h、新測試文件 t.txt ```C #include "bloom.h" #define DICT_FILE "t.txt" ``` - 加入 tst_search 的搜尋時間測量 ```C int bench_test(const tst_node *root, char *out_file, const int max) { char word[WORDMAX] = ""; 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; } while (fscanf(dict, "%s", word) != EOF) { t1 = tvgetf(); tst_search(root,word); t2 = tvgetf(); fprintf(fp, "%d %f nsec\n", idx, (t2 - t1) * 1000000); idx++; } fclose(fp); fclose(dict); return 0; } ``` - bench.c 加入測試有 bloom filter 時的 tst_search 執行時間 ```C int bench_test_bloom(const tst_node *root, char *out_file, const int max,bloom_t bloom) { char word[WORDMAX] = ""; 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; } while (fscanf(dict, "%s", word) != EOF) { t1 = tvgetf(); if (bloom_test(bloom, word) == 1) { tst_search(root, word); t2 = tvgetf(); } else t2 = tvgetf(); fprintf(fp, "%d %f nsec\n", idx, (t2 - t1) * 1000000); idx++; } fclose(fp); fclose(dict); return 0; } ``` - python 程式將 cities.txt 內的字串最後一個字元以 rot13 轉換(shift 13位),輸出為 t.txt ```python import os import string rot13 = string.maketrans( "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz", "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm") fi = open("cities.txt",'r') fo = open("t.txt",'w') for l in fi.readlines(): w_line = "" for x in l.split(','): x = x.strip() i=x[-1:] x=x[:-1]+string.translate(i,rot13) w_line += ','+x fo.write(w_line.strip(',')+'\n') ``` t.txt 內容如下 ``` Shanghnv,Chian Buenos Airrf,Argentian Mumbnv,Indvn Mexico Cigl,Distrito Federny,Mexipb Karacuv,Pakistna İstanbhy,Turkrl Deluv,Indvn Maniyn,Philippinrf Moscbj,Russvn Dhaxn,Bangladefu Seohy,South Korrn São Pauyb,Brazvy Lagbf,Nigervn Jakargn,Indonesvn Toklb,Japna Zhumadina,Chian ``` 假設 bloom filter 能夠直接指出 shift 後的字串不在 tree 中,而未加 bloom filter 時會搜索到最後一個節點才發現搜尋的字串不在 tree中,推 bloom filter 的搜尋方式在這個測試中平均搜尋時間較短,實際測試結果: ``` cpy Mean: 0.3788 Std Dev: 0.3103 ================================ ref Mean: 0.2236 Std Dev: 0.3078 ``` ![](https://i.imgur.com/rzQgpTq.png) 測試結果符合預期,bloom filter 在此時充份發揮功效。 ## Perf ### perf 操作練習:檢查程式在執行過程中各函式佔用 CPU 的時間(cycle)比例 ```shell $ make $ perf record -e branch-misses:u,branch-instructions:u ./test_cpy --bench $ perf report perf.data | sed '/^#/ d' | sed '/^$/d' >> perf.txt $ cat perf.txt # To display the perf.data header info, please use --header/--header-only options. # # # Total Lost Samples: 0 # # Samples: 104K of event 'branch-misses:u' # Event count (approx.): 211066590 # # Overhead Command Shared Object Symbol # ........ ....... ............. ...... # # Samples: 104K of event 'branch-instructions:u' # Event count (approx.): 7059830582 # # Overhead Command Shared Object Symbol # ........ ........ ................ ................................ # 95.52% test_cpy test_cpy [.] tst_suggest 0.73% test_cpy libc-2.23.so [.] _IO_vfscanf 0.70% test_cpy libc-2.23.so [.] __GI___printf_fp_l 0.51% test_cpy libc-2.23.so [.] __mpn_divrem 0.47% test_cpy test_cpy [.] tst_ins_del 0.42% test_cpy libc-2.23.so [.] vfprintf 0.15% test_cpy libc-2.23.so [.] _int_malloc 0.15% test_cpy test_cpy [.] tst_search_prefix 0.13% test_cpy test_cpy [.] tst_free_all 0.11% test_cpy libc-2.23.so [.] __isoc99_fscanf 0.10% test_cpy libc-2.23.so [.] _int_free ```