# Raytracing (2016q1 Homework 2A) ### Reviewed by `HuangWenChen` * 可再使用 OpenMP 多執行緒去執行 * 數據的地方可使用 ` ``` ` 方便閱讀 * github尚未 push 上去 ## gprof學習 [linux分析工具-gprof](http://www.verydemo.com/demo_c167_i10178.html)   [使用Gnu gprof進行程式分析](http://os.51cto.com/art/200703/41426.htm)  [gprof - linux program profiling tool](http://awkzone.blogspot.tw/2010/11/gprof.html) [GNU gprof manual](https://sourceware.org/binutils/docs/gprof/)  gprof是一種GNU profiler**工具,**可以顯示程式進行的;"flatprofile",包含程式執行時各個function的執行時間以及call次數,可以幫助分析程式找到效能瓶頸,加以優化 原理: * 再編譯時加入 -pg ,編譯器會在每個function中加入mcount ( 或 “_mcount” 或 “__mcount”)的函數,所以每個function都會call mmcount ,由此可以做 使用步驟: 1. 編譯時,加入 -pg參數 2. 執行一次程式,會輸出預設為gmon.out的檔案 3. gmon.out會儲存程式執行的資料,function call次數,執行時間,及function互call的次數等等,使用`gprof execution_file` 分析這些數據,做成使用者好讀取的型式 執行: 老師幫我們在Makefile中包好了,如果再編譯時定義PROFILE為1,就會加入 -pg參數 * ponsheng@X55VD:[~/embed/raytracing]$ make PROFILE=1 * cc -std=gnu99 -Wall -O0 -g -pg -c -o objects.o objects.c * cc -std=gnu99 -Wall -O0 -g -pg -c -o raytracing.o raytracing.c * cc -std=gnu99 -Wall -O0 -g -pg -c -o main.o main.c * cc -o raytracing objects.o raytracing.o main.o -lm -pg  * ponsheng@X55VD:[~/embed/raytracing]$ ./raytracing  * # Rendering scene * Done! * Execution time of raytracing() : 7.745289 sec * ponsheng@X55VD:[~/embed/raytracing]$ ls * AUTHORS      LICENSE  Makefile       objects.c  out.png   raytracing    raytracing.o * gmon.out     main.c   math-toolkit.h  objects.h  out.ppm  raytracing.c  result.txt * idx_stack.h  main.o   models.inc objects.o  primitives.h  raytracing.h  use-models.h * ponsheng@X55VD:[~/embed/raytracing]$ gprof raytracing -b > result.txt 執行時間為7.74秒,有生成gmon.out,接著用gprof去分析,-b這個參數代表不顯示統計圖的文字敘述,再把結果導入 result.txt (gprof 指令也可以加入data檔: gprof raytracing gmon.out ,這裡沒加是因為直接使用預設名稱) 執行結果: <undefined style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: medium; letter-spacing: normal;">* **flat profile**</undefined><span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: medium; letter-spacing: normal;"></span> 呈現每個function所耗的時間以及call的次數,由此可知前幾個函式單一執行時間雖然小於0.01秒,卻都執行了1千萬次以上,所以如果要對單一函數做優化,當然是以他們優先,就算只有些微優化,再總時間上就會很明顯的影響: 每次結果,執行時間排序都有些微不同,但是dot_product都是最久的,其餘大於1千萬calls且時間較長的為 dot_product , subtract_vector , multiply_vector   rayRectangularIntersection  normalize add_vector    raySphereIntersection * Flat profile: * Each sample counts as 0.01 seconds. * %   cumulative   self              self     total            * time   seconds   seconds    calls   s/call   s/call  name     * 28.47      1.34     1.34 69646433     0.00     0.00  dot_product * 14.23      2.01     0.67 56956357     0.00     0.00  subtract_vector * 11.26      2.54     0.53 31410180     0.00     0.00  multiply_vector * 8.92      2.96     0.42 13861875     0.00     0.00  rayRectangularIntersection * 7.65      3.32     0.36 10598450     0.00     0.00  normalize * 6.16      3.61     0.29 17836094     0.00     0.00  add_vector * 5.10      3.85     0.24 13861875     0.00     0.00  raySphereIntersection * 3.61      4.02     0.17  4620625     0.00     0.00  ray_hit_object * 3.40      4.18     0.16 17821809     0.00     0.00  cross_product * 1.91      4.27     0.09  4221152     0.00     0.00  multiply_vectors * 1.91      4.36     0.09        1     0.09     4.71  raytracing * 1.70      4.44     0.08  1048576     0.00     0.00  ray_color * ........................................... 疑點:我看manual裡,有說到mcount跟profil這兩個內部function,但是這裡卻沒有顯示出來 <undefined style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: medium; letter-spacing: normal;">* **call graph**</undefined><span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: medium; letter-spacing: normal;"></span> *函式叫用表,左邊為function編號,右邊為有叫用此function的parent,編號可以在最後的索引中找到相對函式 * Call graph * granularity: each sample hit covers 2 byte(s) for 0.22% of 4.49 seconds * index % time    self  children    called     name * <spontaneous> * [1]    100.0    0.00    4.49                 main [1] * 0.05    4.43       1/1           raytracing [2] * 0.01    0.00       1/1           delete_sphere_list [21] * 0.00    0.00       3/3           append_sphere [30] * 0.00    0.00       3/3           append_rectangular [29] * 0.00    0.00       2/2           append_light [31] * 0.00    0.00       1/1           write_to_ppm [35] * 0.00    0.00       1/1           delete_rectangular_list [33] * 0.00    0.00       1/1           delete_light_list [32] * 0.00    0.00       1/1           diff_in_second [34] * ----------------------------------------------- * 0.05    4.43       1/1           main [1] * [2]     99.8    0.05    4.43       1         raytracing [2] * 0.10    4.17 1048576/1048576     ray_color [3] * 0.02    0.14 1048576/1048576     rayConstruction [15] * 0.00    0.00       1/1           calculateBasisVecto * ..................................................................... 用虛線切開的算一個entry,一個entry代表所有跟某個function有關的呼叫,我們用第二個來看,以[X]開頭的這行是primary line,在name欄位的就是這個entry的主角,以上為callers line,以下為subroutine line(children) 用第二個entry舉例: * primary line:  * index % time    self  children    called     name * [2]     99.8    0.05    4.43       1         raytracing [2] %time:佔所有時間比例(含call function) self:執行時間(不含call) =flat profile的時間 children:function call時間 called被呼叫幾次(如果有兩個數字"A+B" A是被外部呼叫,B是遞迴呼叫(自己呼叫) * callers line: * index % time    self  children    called     name * 0.05    4.43       1/1           main [1] self&childen:同primay called:呼叫raytracing的函式,佔的比例(不包含遞迴) name:如果caller無法被定義,就印<spontaneous>,ex.main * subroutine line * index % time    self  children    called     name * 0.10    4.17 1048576/1048576     ray_color [3] * 0.02    0.14 1048576/1048576     rayConstruction [15] * 0.00    0.00       1/1           calculateBasisVecto self:由父函式呼叫的子函式總執行時間 children:由父函式呼叫時,子函式呼叫別人所花的總時間 called:子函式被父函式呼叫的比例 要閱讀這個graph其實不容易,如果想要簡易明瞭的_gprof2dot_ [](https://github.com/jrfonseca/gprof2dot)https://github.com/jrfonseca/gprof2dot [同學共筆](https://embedded2016.hackpad.com/2016q1-Homwork2a--QixiAsqbVaf#:h=gprof2dot)  <undefined style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: medium; letter-spacing: normal;">* **Index by function name**</undefined><span style="color: rgb(0, 0, 0); font-family: Helvetica; font-size: medium; letter-spacing: normal;"></span> 函式編號 * Index by function name * [11] add_vector (math-toolkit.h) [22] fresnel (raytracing.c) [15] rayConstruction (raytracing.c) * [31] append_light           [24] idx_stack_empty (idx_stack.h) [5] rayRectangularIntersection (raytracing.c) * [29] append_rectangular     [27] idx_stack_init (idx_stack.h) [7] raySphereInterse * ............................................. 以下為沒有加入-pg編譯的執行時間 * Execution time of raytracing() : 4.156151 sec 目標就是把時間降低吧~ ## 了解raytracing  math-toolkit.h: 由gprof可知raytracing的熱點為:  dot_product , subtract_vector , multiply_vector   rayRectangularIntersection  normalize add_vector    raySphereIntersection  math-toolkit.h:  dot_product , subtract_vector , multiply_vector  normalize add_vector raytracing.c: rayRectangularIntersection   raySphereIntersection   ->無規則非迴圈 ## 優化方式: **原版** 執行時間: * Execution time of raytracing() : 4.156151 sec gprof: * Each sample counts as 0.01 seconds. * %   cumulative   self              self     total            * time   seconds   seconds    calls   s/call   s/call  name     * 28.47      1.34     1.34 69646433     0.00     0.00  dot_product * 14.23      2.01     0.67 56956357     0.00     0.00  subtract_vector * 11.26      2.54     0.53 31410180     0.00     0.00  multiply_vector * 8.92      2.96     0.42 13861875     0.00     0.00  rayRectangularIntersection * 7.65      3.32     0.36 10598450     0.00     0.00  normalize * 6.16      3.61     0.29 17836094     0.00     0.00  add_vector * 5.10      3.85     0.24 13861875     0.00     0.00  raySphereIntersection * 3.61      4.02     0.17  4620625     0.00     0.00  ray_hit_object * 3.40      4.18     0.16 17821809     0.00     0.00  cross_product * 1.91      4.27     0.09  4221152     0.00     0.00  multiply_vectors * 1.91      4.36     0.09        1     0.09     4.71  raytracing * 1.70      4.44     0.08  1048576     0.00     0.00  ray_color * ........................................... **Loop Unrolling** 為了減少loop的預測錯誤,展開loop內容,減少loop次數,會因此而縮短執行時間,卻也會讓程式體積變大,是一種以空間換取時間的方式 舉例:dot_product * double dot_product(const double *v1, const double *v2) * { * double dp = 0.0; * for (int i = 0; i < 3; i++) * dp += v1[i] * v2[i]; * return dp; * } 把for loop拆開: * double dot_product(const double *v1, const double *v2) * { * double dp = 0.0; * dp += v1[0] * v2[0]; * dp += v1[1] * v2[1]; * dp += v1[2] * v2[2]; * return dp; * } 能實現優化的函式:add_vector, subtract_vector, multiply_vectors, multiply_vector , dot_product  執行時間: * Execution time of raytracing() : 2.887629 sec gprof分析 * %   cumulative   self              self     total            * time   seconds   seconds    calls   s/call   s/call  name     * 31.35      0.99     0.99 69646433     0.00     0.00  dot_product * 13.30      1.41     0.42 10598450     0.00     0.00  normalize * 11.72      1.78     0.37 13861875     0.00     0.00  rayRectangularIntersection * 10.45      2.11     0.33 13861875     0.00     0.00  raySphereIntersection * 5.86      2.30     0.19 56956357     0.00     0.00  subtract_vector * 5.70      2.48     0.18 31410180     0.00     0.00  multiply_vector * 4.75      2.63     0.15 17821809     0.00     0.00  cross_product * 4.59      2.77     0.15  4620625     0.00     0.00  ray_hit_object * 3.80      2.89     0.12  1048576     0.00     0.00  ray_color * 3.48      3.00     0.11 17836094     0.00     0.00  add_vector 在計組有學過prediction,記得loop的prediction會預測forward,所以可以減少猜錯,或是用1-2 bits,去記憶forward或backward,但是因為這裡只有loop3次,沒辦法很確定看出在-O0時式採用哪一種,所以要用一個大一點的for loop來驗證 * 驗證 如果要將大forloop做loop unrolling,可以使用Duff’s device,會是一個較簡潔的辦法  [Loop unrolling, Duff’s device](https://www.ptt.cc/bbs/C_and_CPP/M.1246071002.A.A54.html)  ** inline 函數** [C99的inline function ](http://wen00072-blog.logdown.com/posts/196568-c99-inline-function) 在 math-toolkit.h 中,每個函數前面都有 `static inline` * Static:  * [靜態函數](http://cg2010studio.com/2011/06/27/cc-%E9%9D%9C%E6%85%8B%E5%87%BD%E5%BC%8F-static-function/)  * 因為這個.h檔可能被多個檔案引用,若不設為static,會造成在linking成執行檔時發生找到多個重複function的錯誤,所以宣告為靜態數static,只讓function在header裡能被找到,減少錯誤, * Inline: * inline 是一個keyword,提醒compiler可以將function本體直接填入呼叫該function的位置 * 優點為: * 減少呼叫function的overhead,如參數傳遞、回傳值處理等。 * 切換到function會jump,也就是會有branch的行為。因為CPU的pipeline和cache的特性,  會影響效能。 * 經由代換程式碼,也許可以增加最佳化的可能性。 知道了 inline 是建議編輯器在函數很小的時候可以直接內嵌進程式碼,藉此減少呼叫函數的 overhead 。 因為是「建議」,所以編輯器可以忽略你的建議,此外,這個case老師規定編器不能開最佳化,要用-O0,這時即使被宣告inline,任何函數都不會被展開,所以要強制編譯器 inline 在程式碼中加入 `__attribute__ ((always_inline))` 來達到強制 inline * double __attribute__ ((always_inline)) dot_product(const double *v1, const double *v2) 能實現優化的函式:all 執行時間: * Execution time of raytracing() : 4.050906 sec gprof分析 * Each sample counts as 0.01 seconds. * %   cumulative   self              self     total            * time   seconds   seconds    calls   s/call   s/call  name     * 30.11      1.27     1.27 13861875     0.00     0.00  rayRectangularIntersection * 20.62      2.14     0.87 13861875     0.00     0.00  raySphereIntersection * 12.09      2.65     0.51  2110576     0.00     0.00  compute_specular_diffuse * 8.06      2.99     0.34  2110576     0.00     0.00  localColor 跟[原版](https://embedded2016.hackpad.com/2016q1-Homework-2-A-x4ha67f9VTu#:h=%E5%8E%9F%E7%89%88) 相比math-toolkit.h裡的funtion都不見了,其餘的時間都增加了,明顯就是展開後,就不算是function call了,時間都分攤在caller上,所以減少的時間就是function call造成的overhead **POSIX Thread ** **使用SIMD指令** **OPENMP** ## 作業目標 * 以 make PROFILE=1 重新編譯程式碼,並且學習 [gprof](https://sourceware.org/binutils/docs/gprof/) * 參考 [使用 GNU gprof 進行 Linux 平台的程式分析](http://os.51cto.com/art/200703/41426.htm) * 以 [gprof](https://sourceware.org/binutils/docs/gprof/) 指出效能瓶頸,並且著手改寫檔案 math-toolkit.h 在內的函式實做,充分紀錄效能差異在共筆  * 注意: 請勿更動編譯器最佳化選項 -O0 (關閉最佳化) * 檔案 math-toolkit.h 定義的若干數學操作函式很重要,可參考 [SIMD optimized dot and cross product functions](http://tomjbward.co.uk/simd-optimized-dot-and-cross/) 和 [2015q3 Homework #1 Ext](http://wiki.csie.ncku.edu.tw/embedded/2015q3h1ext) * 可善用 POSIX Thread, OpenMP, software pipelining, 以及 loop unrolling 一類的技巧來加速程式運作 * 將你的觀察、分析,以及各式效能改善過程,並善用 gnuplot 製圖,紀錄於「[作業區](https://embedded2016.hackpad.com/2016q1-Homework-2-v37rXPJjRlW)」,記得標注「開發紀錄(A)」