# 2018q1 Homework2 (prefix-search) contributed by <`ryanpatiency`> ###### tags: `ryanpatiency` `homework` 前往 [課程 wiki](http://wiki.csie.ncku.edu.tw/sysprog/schedule) ## 環境 ``` Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 4 L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 3072K ``` ## 實驗大綱 * 目的:改進 test_ref.c , 使其效能比 test_cpy.c 還要好 * 特別要求:可以使用 make bench 直接比較 * 我想做的實驗:(操縱變因) * Traverse to generate suggestion : 連 memory pool 都不用 * Traverse to generate suggestion version 2: 改變 api 以求更快的速度 * 測量方法:(應變變因) * 時間 `tvgetf` * 額外實驗: * 為什麼要用 `tvgetf()` 不用 `clock()` 呢? * gcc 會不會自動將 len-1 和 --len 合併? * gcc 能不能將某些 if else 自動改變成 a=?b:c * echo 3 | tee ... 真的有差別嗎? * Improve data: 目前國家和城市放在一起,而且 "," 符號也會放進去,我想處理這個問題 ## 實驗: Traverse to generate suggestion ### 原理: * 不複製字串於最後,節省一半的空間。 * 在 suggest 時動態 malloc suggest 所需的字串 ### 預測實驗結果: * 節省空間。因為不需要為每一個節點複製字串 * `tst_ins_del` 的時間變快,同樣的道理 * `tst_search_prefix` 的時間變慢,因為需要動態 malloc; ### 實驗步驟 - [x] 更改原本 `tst_ins_del` 的程式碼,將最後 `node-eqkid` 改為 `NULL` - [x] 實作 `tst_traverse` 的程式碼,能將參數節點以下的所有字串複製到參數 `char **a` 中,當然需要 `malloc` - [x] 更改 `tst_search_prefix` 的程式碼,將 `tst_suggest` 改成 `tst_traverse` #### 測量時間 -- build tree 我寫了 shell script, 分別跑 100 次。對建立樹的平均值做圖如下: ![](https://i.imgur.com/IikoXsV.png) #### 測量空間 -- build tree 我使用 valgrind, 發現結果如下 ``` $ valgrind ./test_cpy total heap usage: 588,254 allocs, 588,254 frees, 16,969,720 bytes allocated $ valgrind ./test_ref total heap usage: 502,754 allocs, 502,754 frees, 16,094,696 bytes allocated ``` 對空間做圖如下: ![](https://i.imgur.com/FnwGkyO.png) #### 測量時間 -- prefix search 用 city 的前3個字查詢,看結果如何。 我寫了一個程式 gen_cmd.c 產生 command.txt 和一個 shell script 計算 measure prefix time * 將執行時間分成十份做圖: ![](https://i.imgur.com/BXjNQC6.png) ### 實驗結論 原來相比整個樹來說,`strdup` 的 size 可說微乎其微。因為每一個 node 都有 lokid, hikid, eqkid 加起來就 8*3 = 24 bytes 了。而 key 才一個 byte 而已。所以時間、空間都只好一點而已。 ## 實驗:Traverse to generate suggestion version 2 ### 觀察: 如果不為 array alloc memory dynamically,可能可以快一些吧 ### 方法: 新增 quick_tst_search_prefix 的 api,省略 1. char **a, 2. int *n, 3. const int max 直接產生 suggestion, 測量速度。 ### 實驗結果: ![](https://i.imgur.com/YO76l1m.png) :::danger version 2 慢了很多,可以發現到使用 array + malloc + stack 的時間仍然 < printf ::: # 額外實驗 這部份的實驗和改善時間空間沒直接關係 ## 實驗: 改善 data ### 觀察: 現在的 data 將 city 和 country 混在一起。我就順手把他改過來了: ### 原本的: ``` suggest[885] : Vizze, suggest[886] : Vizzini, suggest[887] : Vizzolo ``` ### 後來的: ``` suggest[1021] : Villa Santa Rita, Argentina suggest[1022] : Villa Santina, Italy suggest[1023] : Villa Santo Stefano, Italy ``` 原本的有 `,` 等莫名其妙的資料,修改完就好多了。 ## 實驗: echo 3 | tee ... 的影響 ### 目的: 了解cache 的影響, ### 方法: 將上面提到的 shell script 註解掉 echo... 的部份 ### 實驗結果 對 test_cpy 的做圖如下 ![](https://i.imgur.com/7ckRHyL.png) 可以發現,有 cache 真的會比較快一些 ## 實驗:使用 `clock()` 不用 `tvgetf()` 經過實驗,大部份的 tst_prefix_search 的執行都只要 1 cycle or 2 cycle, 不能精確比較。 ## 實驗:觀察 assembly, 確認 gcc 能不能將某些 if else 自動改變成 a=?b:c txt.c 中,有些 if else 可以更精簡。由於學過 branch prediction 的 penalty 很大,想了解 gcc -O3 的特性 測試對象: ```clike= else if (diff < 0) { curr = (curr->lokid); } else { curr = (curr->hikid); } ``` 改成 ```clike= else { curr = (diff < 0) ? (curr->lokid):(curr->hikid); } ``` Makefile 改成: `CFLAGS = -g -O3` 原本的 ``` 304 } else if (diff < 0) /* handle the less than case */ 0000000000401cb0: js 0x401cc0 <tst_search+48> 307 curr = curr->hikid; /* handle the greater than case */ 0000000000401cb2: mov 0x18(%rdi),%rdi 0000000000401cb6: jmp 0x401c90 <tst_search> 0000000000401cb8: nopl 0x0(%rax,%rax,1) 305 curr = curr->lokid; 0000000000401cc0: mov 0x8(%rdi),%rdi 0000000000401cc4: jmp 0x401c90 <tst_search> 0000000000401cc6: nopw %cs:0x0(%rax,%rax,1) ``` 後來的 ``` 306 curr = (diff < 0) ? curr->lokid : curr->hikid; 0000000000401cb0: js 0x401cc0 <tst_search+48> 0000000000401cb2: mov 0x18(%rdi),%rdi 0000000000401cb6: jmp 0x401c90 <tst_search> 0000000000401cb8: nopl 0x0(%rax,%rax,1) 0000000000401cc0: mov 0x8(%rdi),%rdi 0000000000401cc4: jmp 0x401c90 <tst_search> 0000000000401cc6: nopw %cs:0x0(%rax,%rax,1) ``` ### 結論: gcc 在 -O3 的情況下能將這一部份最佳化 ## 實驗:觀察 assembly, gcc 會不會自動將 len-1 和 --len 合併? test_xxx 中,這幾行看起來怪怪的, ```clike= static void rmcrlf(char *s) { size_t len = strlen(s); if (len && s[len - 1] == '\n') s[--len] = 0; } ``` 我把他改成 ```clike= static void rmcrlf(char *s) { size_t len = strlen(s); if (len && s[--len] == '\n') s[len] = 0; } ``` 觀察實驗如下: 原本的: ``` rmcrlf: 0000000000400a3c: push %rbp 0000000000400a3d: mov %rsp,%rbp 0000000000400a40: sub $0x20,%rsp 0000000000400a44: mov %rdi,-0x18(%rbp) 30 size_t len = strlen(s); 0000000000400a48: mov -0x18(%rbp),%rax 0000000000400a4c: mov %rax,%rdi 0000000000400a4f: callq 0x400810 <strlen@plt> 0000000000400a54: mov %rax,-0x8(%rbp) 31 if (len && s[len - 1] == '\n') 0000000000400a58: cmpq $0x0,-0x8(%rbp) 0000000000400a5d: je 0x400a88 <rmcrlf+76> 0000000000400a5f: mov -0x8(%rbp),%rax 0000000000400a63: lea -0x1(%rax),%rdx 0000000000400a67: mov -0x18(%rbp),%rax 0000000000400a6b: add %rdx,%rax 0000000000400a6e: movzbl (%rax),%eax 0000000000400a71: cmp $0xa,%al 0000000000400a73: jne 0x400a88 <rmcrlf+76> 32 s[--len] = 0; 0000000000400a75: subq $0x1,-0x8(%rbp) 0000000000400a7a: mov -0x18(%rbp),%rdx 0000000000400a7e: mov -0x8(%rbp),%rax 0000000000400a82: add %rdx,%rax 0000000000400a85: movb $0x0,(%rax) ``` 後來的 ``` rmcrlf: 0000000000400a3c: push %rbp 0000000000400a3d: mov %rsp,%rbp 0000000000400a40: sub $0x20,%rsp 0000000000400a44: mov %rdi,-0x18(%rbp) 0000000000400a48: mov -0x18(%rbp),%rax 0000000000400a4c: mov %rax,%rdi 0000000000400a4f: callq 0x400810 <strlen@plt> 0000000000400a54: mov %rax,-0x8(%rbp) 31 if(len && s[--len] == '\n') 0000000000400a58: cmpq $0x0,-0x8(%rbp) 0000000000400a5d: je 0x400a84 <rmcrlf+72> 0000000000400a5f: subq $0x1,-0x8(%rbp) 0000000000400a64: mov -0x18(%rbp),%rdx 0000000000400a68: mov -0x8(%rbp),%rax 0000000000400a6c: add %rdx,%rax 0000000000400a6f: movzbl (%rax),%eax 0000000000400a72: cmp $0xa,%al 0000000000400a74: jne 0x400a84 <rmcrlf+72> 32 s[len] = 0; 0000000000400a76: mov -0x18(%rbp),%rdx 0000000000400a7a: mov -0x8(%rbp),%rax 0000000000400a7e: add %rdx,%rax 0000000000400a81: movb $0x0,(%rax) ``` ### 實驗結論: 預設情形下,不同程式結果不同 ## 測試 gcc `-O3` 時的行為 同樣的實驗,但是 Makefile 改成: `CFLAGS = -g -O3` 原本的 ``` rmcrlf: 00000000004010c0: mov %rdi,%rdx 00000000004010c3: mov (%rdx),%ecx 00000000004010c5: add $0x4,%rdx 00000000004010c9: lea -0x1010101(%rcx),%eax 00000000004010cf: not %ecx 00000000004010d1: and %ecx,%eax 00000000004010d3: and $0x80808080,%eax 00000000004010d8: je 0x4010c3 <rmcrlf+3> 00000000004010da: mov %eax,%ecx 00000000004010dc: shr $0x10,%ecx 00000000004010df: test $0x8080,%eax 00000000004010e4: cmove %ecx,%eax 00000000004010e7: lea 0x2(%rdx),%rcx 00000000004010eb: mov %eax,%esi 00000000004010ed: cmove %rcx,%rdx 00000000004010f1: add %al,%sil 00000000004010f4: sbb $0x3,%rdx 31 if(len && s[--len] == '\n') 00000000004010f8: sub %rdi,%rdx 00000000004010fb: je 0x401107 <rmcrlf+71> 00000000004010fd: lea -0x1(%rdi,%rdx,1),%rax 0000000000401102: cmpb $0xa,(%rax) 0000000000401105: je 0x401110 <rmcrlf+80> 0000000000401107: repz retq 0000000000401109: nopl 0x0(%rax) 32 s[len] = 0; 0000000000401110: movb $0x0,(%rax) 0000000000401113: retq 0000000000401114: nopw %cs:0x0(%rax,%rax,1) 000000000040111e: xchg %ax,%ax ``` 後來的 ``` rmcrlf: 00000000004010c0: mov %rdi,%rdx 00000000004010c3: mov (%rdx),%ecx 00000000004010c5: add $0x4,%rdx 00000000004010c9: lea -0x1010101(%rcx),%eax 00000000004010cf: not %ecx 00000000004010d1: and %ecx,%eax 00000000004010d3: and $0x80808080,%eax 00000000004010d8: je 0x4010c3 <rmcrlf+3> 00000000004010da: mov %eax,%ecx 00000000004010dc: shr $0x10,%ecx 00000000004010df: test $0x8080,%eax 00000000004010e4: cmove %ecx,%eax 00000000004010e7: lea 0x2(%rdx),%rcx 00000000004010eb: mov %eax,%esi 00000000004010ed: cmove %rcx,%rdx 00000000004010f1: add %al,%sil 00000000004010f4: sbb $0x3,%rdx 31 if(len && s[--len] == '\n') 00000000004010f8: sub %rdi,%rdx 00000000004010fb: je 0x401107 <rmcrlf+71> 00000000004010fd: lea -0x1(%rdi,%rdx,1),%rax 0000000000401102: cmpb $0xa,(%rax) 0000000000401105: je 0x401110 <rmcrlf+80> 0000000000401107: repz retq 0000000000401109: nopl 0x0(%rax) 32 s[len] = 0; 0000000000401110: movb $0x0,(%rax) 0000000000401113: retq 0000000000401114: nopw %cs:0x0(%rax,%rax,1) 000000000040111e: xchg %ax,%ax ``` 一模一樣 ### 實驗結論: gcc 在 -O3 時能將這一部份最佳化。 ## 參考 這一部份是我找到的,對我自己有幫助的資訊 * https://hackmd.io/s/BkiEdEj3-#Reviewed-by-amikai 在畫圖上面有值得參考的地方 * C 有提供 `getopt.h` 幫助編程者管理 option, 直接用 `argv` 取得 argument 不夠嚴謹 * https://hackmd.io/s/SkeKQEq3Z 實驗紀錄的很清楚,值得重複 * https://hackmd.io/K4v3wF-8SFudCT2-ckK9Hg?both 紀錄 tina 程式碼的缺陷,如果有人想重複 tina 同學的實驗請先參考這篇 ## 問題 * Makefile