2016q3 Homework5 - JIT compiler === contributed by <`oiz5201618`>, <`ierosodin`> ###### tags: `sysprog-hw` `JIT compiler` # 作業要求 * 作在 github 上 fork [jit-construct](https://github.com/sysprog21/jit-construct),注意不要跟 2015 年的作業搞錯了 * 依據上方提示,提出改善內建 benchmark suite 效能的機制,並且要能夠充分解釋行為,需要一併透過 gnuplot 自動產生效能分析圖表 * 參考以下實做,對 Brainfuck 程式語言進行擴充,使其能夠支援 concurrency / parallelism。需要有程式語言的規格描述並且驗證。 * [Bukkake](https://bitbucket.org/wjmelements/bukkake): A parallel brainfuck JIT in C * [Brainfuck Process Extensions](http://www.kjkoster.org/BFPX/Brainfuck_Process_Extensions.html) * [Parallel Brainfuck](https://github.com/cmdli/parallel-brainfuck) * [Concurrent Brainfuck](http://www.schabi.de/cbf/) --- # Brainfuck Instructions Brainfuck 的基本模型是由一個==連續的記憶體位置==以及一個==指向記憶體的指標==所組成 >這八種指令組成的 BF ,符合圖靈機的基本思想 [Wikipedia](https://en.wikipedia.org/wiki/Turing_machine) [name=ierosodin] ## 八種 Brainfuck 的指令: ![](https://i.imgur.com/P4jM0IJ.png) --- # Compiler 一般編譯器的架構可以簡單拆成一個或數個 frontends ,以及一個或數個 backends : ```graphviz digraph finite_state_machine { rankdir=LR; size="8,5" node [shape = ellipse]; source_code, executable; node [ shape="box" ]; source_code -> Frontend [ label = "" ]; Frontend -> Optimizer [ label = "IR" ]; Optimizer -> Backend [ label = "IR" ]; Backend -> executable [ label = "" ]; } ``` Frontend 先將原始碼轉換為一個或多個的 IR ,方便進行最佳化,再由 Backend 將 IR 轉換成執行程式。 * [Intermediate Representation wikipedia](https://en.wikipedia.org/wiki/Intermediate_representation) > C語言作為許多作業系統的 System programming language ,很多其他的語言都使用它作為 IR [name=ierosodin] > 中介碼(IR,intermediate representation)[name=阿Hua] # Objdump [陳鐘誠教授](http://ccckmit.wikidot.com/gnu:objdump):Objdump 是 GNU 的主要目的檔觀察工具, 您可用 objdump 顯示目的檔的檔頭、區段、內容、符號表等資訊, 表格一顯示了其使用方法。 ![](https://i.imgur.com/pNTG3Xx.png) * [objdump(1) - Linux man page](https://linux.die.net/man/1/objdump) --- # 優化策略 * [brainfuck optimization strategies](http://calmerthanyouare.org/2015/01/07/optimizing-brainfuck.html) * `Contraction` * `Clear loops` * `Copy loops` * `Multiplication loops` * `Scan loops` * `Operation offsets` ``` clear [-] ``` ``` Copy loops [->+>+<<] ``` > 由於P[0]為判斷條件跟複製目標,其值將會遞減改變,因此將P[0]中的值複製到P[1]、P[2]之中,來達到複製的目的。 > [name=阿Hua] ``` Multiplication loops [->+++<] or [->+++>+++++++<<] ``` > Mul的操作視需求可以資料數 > 如上`[->+++>+++++++<<]`效果等同於 > ``` > P[1] += P[0] * 3; > P[2] += P[0] * 7; > P[0] = 0; > ``` > `+=`原因是因為若`P[1]、P[2]`中有值,就會直接累加 > 想要清空`P[1]、P[2]`就可以搭配上面的 copy loop 來使用 > [name=阿Hua] # About DynASM * generating machine code at runtime * JIT 利用 DynASM 動態產生 machine code 並將它放入 `dasm_State` (at runtime) ,最後再轉換成可執行的機器碼 * `|` 有這個開頭的表示, DynASM 的 preprocessor 會先進行處理 * `|.arch ` 在每一個 DynASM source file 中,必須先指定產生哪種架構的 machine code ,例如 x86 或 x64 * `.define (for simple substitutions)` * `.macro (for more complex constructions)` * `dasm_State` 用來存 DynASM 的狀態 * `initjit()` 包含了 `dasm_init(state, 1)` 與 `dasm_setup(state, actionlist)`,前者為配置 DynASM state 的空間,後者則是完成 DynASM state 的初始化 透過 brainfuck 做出來的碎形 ![](https://i.imgur.com/K8OgSTp.png) --- # 針對 jit-x64 進行效能優化 ## 未優化版本 ``` progs/awib.b GOOD 89.4ms progs/mandelbrot.b GOOD 3486.2ms progs/hanoi.b GOOD 8919.4ms ``` ![](https://i.imgur.com/gJBn2gU.png) ## Contraction > 請在分組頁面中補上 github 連結 > [color=red][name=課程助教] > >OK [color=orange][name=阿Hua & ierosodin] > >>.[color=yellow] > >> >.[color=green] > >> > >.[color=blue] > >> > >>.[color=purple] 此方法是將多餘運算整併,例如 +++++ ,其實就是 += 5,此改善方法雖然直覺簡單,但實質效益也不差,而實作方法可在遇到+ - > < 時即檢查是否為連續出現。 ```clike= int continuous_count(char *p) { char *ptr = p; int count = 0; while (*ptr == *p) { count++; ptr++; } return count; } ``` 在 main 中修改成如下,包含 >、<、+、- ```clike= case '+': count = continuous_count(p); p += count - 1; | add byte [PTR], count break; ``` ``` progs/awib.b GOOD 51.0ms progs/mandelbrot.b GOOD 1276.1ms progs/hanoi.b GOOD 5175.9ms ``` ![](https://i.imgur.com/QDHPjZr.png) 我們使用 if else 判斷,保留 inc 在只有出現一次的時候 ```clike= case '+': count = continuous_count(p); if(count == 1){ | inc byte [PTR] }else{ p += count - 1; | add byte [PTR], count } break; ``` 效能如下,可以發現在 awib.b、mandelbrot.b 效能反而變差,而只有 hanoi.b 效能變佳 我們發現 hanoi.b 相較於其他兩者有更大量的 [ - ] 操作,所以 inc 對比於 add 指令操作的優化會凸顯出來,但使用到其他兩者則沒有反而因為條件判斷使得效能降低。 ``` progs/awib.b GOOD 59.0ms progs/mandelbrot.b GOOD 1295.9ms progs/hanoi.b GOOD 4529.9ms ``` ![](https://i.imgur.com/RAbRQuj.png) > - [ ] TODO:將圖表換成倍數 [color=blue][name=阿Hua] --- ## Clear loop 遇到 [ - ] 的情況直接將PTR給予的值給 0 ,然後跳過 ```clike= int clear_loop(char *p){ if (*(p+1) == '-' && *(p+2) == ']'){ return 1; }else{ return 0; } } case '[': if (top == limit) err("Nesting too deep."); if(clear_loop(p)){ p += 2; | mov byte [PTR], 0 break; }else{ maxpc += 2; *top++ = maxpc; dasm_growpc(&state, maxpc); | cmp byte [PTR], 0 | je =>(maxpc-2) |=>(maxpc-1): break; } ``` ``` progs/awib.b GOOD 41.1ms progs/mandelbrot.b GOOD 1237.5ms progs/hanoi.b GOOD 137.1ms ``` ![](https://i.imgur.com/8H7HFzS.png) --- # Clear Loop & Multiple Loop & Copy Loop 因為這三者都和 while 迴圈有關,[參考老師](https://hackmd.io/s/SJ7AZJv1e) 的 check_loop() 進行修改,合併三個功能 >因為是用 `read_file(argv[1])` 讀取.b檔的字元,需要考慮到程式碼換行的問題( `'\n'` 也算一個字元) [name=ierosodin] > Multiple loop 的組語本身必須考慮到位元的配合,才不會因為搬動過多的記憶體,讓運算出錯 >利用 global variable `global_count` 來計算 p 指標確切的位移量,避免遇到 '\n' 或空白所造成 offset 計算上的錯誤 Check loop 的修改 ```clike= int check_loops(char *p,int *index,int *mult) { int res,offset = 0,_index = 0; global_count = 0; if (*(p+1) != '-') return -1; p += 2; global_count += 2; while (*p != ']') { if (*p == '[' || *p == '-' || *p == '.' || *p == ',') return -1; res = continuous_count(p); if (*p == '>') offset += res; else if (*p == '<') offset -= res; else if (*p == '+') { index[_index] = offset; mult[_index] = res; _index++; } global_count += res; p += res; } if (offset != 0) return -1; return _index; } ``` main 裡面組語的修改 ```clike= case '[': if (top == limit) err("Nesting too deep."); count = check_loops(p, index, mult); if(count == 0){ p += global_count; | mov byte [PTR], 0 break; }else if(count > 0){ | mov cx, word [PTR] | mov r11, PTR | add PTR, index[0] | mov ax, word mult[0] | imul ax, cx | add byte [PTR], al for(int i = 1; i < count; i++){ | mov r9, index[i] | sub r9, index[i - 1] | add PTR, r9 | mov ax, mult[i] | imul ax, cx | add byte [PTR], al } | mov PTR, r11 | mov byte [PTR], 0 p += global_count; break; }else{ maxpc += 2; *top++ = maxpc; dasm_growpc(&state, maxpc); | cmp byte [PTR], 0 | je =>(maxpc-2) |=>(maxpc-1): break; } ``` ``` progs/awib.b GOOD 39.3ms progs/mandelbrot.b GOOD 1351.9ms progs/hanoi.b GOOD 97.8ms ``` ![](https://i.imgur.com/J9L3RJ5.png) > 因為每次遇到換行 '\n' 時, check loop 都會額外再調用 continuous_count 來檢查;而且當一連串的 ++++ 遇到換行時, 也會變成兩次運算,因此還有能夠優化的空間 > [name=阿Hua] > 從比較圖中可以發現,加入 check_loops 功能之後,碎形運算的效能反而降低了,還在思考 code 與優化方法之間的影響 [name=ierosodin] --- 將換行符號考慮進去後的效能表現, hanoi 再效能上有較穩定的變快,但其他兩者的數字很浮動,但整體的最快速度都是變快的 ``` progs/awib.b GOOD 32.3ms progs/mandelbrot.b GOOD 1329.1ms progs/hanoi.b GOOD 86.7ms ``` ![](https://i.imgur.com/x8EbPtz.png) > - [ ]TODO:必須加入數學統計模型[color=blue][name=阿Hua] --- # 參考資料 * [廖健富共筆](https://hackpad.com/2015q3-Homework-4B-5I46HyqOCGJ#:h=Jit-construct) * [林郁寧共筆](https://embedded2015.hackpad.com/-Homework4-B-h5FfVE6RTeu) * [作業共筆彙整](https://embedded2015.hackpad.com/2015q3-Homework-4-8AvSmXDYC38) * [brainfuck optimization strategies](http://calmerthanyouare.org/2015/01/07/optimizing-brainfuck.html) * [Lua 學習筆記 (一) : 基本語法](http://yhhuang1966.blogspot.tw/2015/07/lua_14.html) * [The Unofficial DynASM Documentation](https://corsix.github.io/dynasm-doc/reference.html#dynasm_lua) * [中英對照表](http://jjhou.boolan.com/terms.htm) * [x86 Assembly Guide](http://www.cs.virginia.edu/~evans/cs216/guides/x86.html) * [dynasm](https://luapower.com/dynasm) * [mov in assembly](http://slvs.tc.edu.tw/~baochyi/teacher/assembly/c0402.htm) * [PC Assembly Language 學習筆記(8) - Arrays](http://godleon.blogspot.tw/2008/02/assembly-segment.html) * [Arrays in Assembly Language](http://www.cs.uwm.edu/classes/cs315/Bacon/Lecture/HTML/ch12s04.html)