# 2016q3 Homework1 (raytracing) contributed by <`vic85821`> ### Reviewed by `0140454` * 應該善用 .gitignore 來排除測試時由程式所產生的檔案。 例如 [commit "modify by Macro"](https://github.com/vic85821/raytracing/commit/ee19e2359821a9b674a4cbe36f127fedf282d110) 中的 `output_lu.txt`。 * 應該把非必要的註解刪除後再 commit。 例如 [commit "use OpenMP to improve"](https://github.com/vic85821/raytracing/commit/320cd9376a31ee8fdb7f625cdda09f540a6903cb)。 * 可使用 **gnuplot** 來輸出效能比較圖。 * 未來可嘗試 SIMD 指令來優化並分析其效能及應用環境等。 ## 開發環境 * Ubuntu 16.04.1 LTS * Linux version 4.4.0-38-generic * CPU : Intel® Core™ i5-3230M CPU @ 2.60GHz * MEM : 8GB * L1d cache : 32K * L1i cache : 32K * L2 cache : 256K * L3 cache : 3072K ## 事前準備 * 預先裝好軟體 ```shell $ sudo apt-get update $ sudo apt-get install graphviz $ sudo apt-get install imagemagick ``` ## 目標 雖然作業很多QAQ,但對我來說,就算都不太懂,只要有學到新的知識,儘管是名詞定義,一點一滴慢慢的跟上,一學期下來一定不同於其他的課程的收穫量!!!!! # 案例分析 * 在優化之前,先來執行一次看看效能&時間 `Execution time of raytracing() : 3.020114 sec` * 若使用gprof ```shell $ make PROFILE=1 ``` `Execution time of raytracing() : 6.545642 sec` 產生 `gmon.out` 透過 `gprof ./raytracing | less` 觀察各個function的呼叫次數與使用時間 * `less` 可以讓結果分頁,有助於觀察 ```shell Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 21.96 0.54 0.54 69646433 0.00 0.00 dot_product 15.86 0.93 0.39 56956357 0.00 0.00 subtract_vector 10.58 1.19 0.26 10598450 0.00 0.00 normalize 9.97 1.44 0.25 13861875 0.00 0.00 rayRectangularIntersection 9.97 1.68 0.25 13861875 0.00 0.00 raySphereIntersection 7.93 1.88 0.20 31410180 0.00 0.00 multiply_vector 7.52 2.06 0.19 17836094 0.00 0.00 add_vector 4.88 2.18 0.12 4620625 0.00 0.00 ray_hit_object 3.25 2.26 0.08 17821809 0.00 0.00 cross_product 2.44 2.32 0.06 1048576 0.00 0.00 ray_color 1.83 2.37 0.05 4221152 0.00 0.00 multiply_vectors 0.81 2.39 0.02 2110576 0.00 0.00 localColor 0.81 2.41 0.02 1241598 0.00 0.00 protect_color_overflow ``` * 偷偷的利用編譯器的最佳化,看一下執行時間(將Makefile裡面的 `-O0` 改成 `-Ofast`) `Execution time of raytracing() : 0.670504 sec` >目標就是超越編譯器最佳化了!!(~~這個執行速度好快R~ QAQ~~) * 開始著手優化!!! ## Loop unrolling 看完功課介紹的video,馬上就來試試看,暴力把LOOP展開!! * note : [loop unrolling](https://zh.wikipedia.org/wiki/%E5%BE%AA%E7%8E%AF%E5%B1%95%E5%BC%80) * 減少branch prediction 可以增加效率 * 過多會讓程式可讀性下降 (coding 金字塔!! - readable) * 透過gprof可以找出較佔時間的前幾名 * 將math-toolkit.h定義的一些數學運算function()展開 * e.g. `add_vector()`, `substract_vector()` ```c /* 舉例 dot_product */ double dot_product(const double *v1, const double *v2) { double dp = 0.0; dp = v1[0] * v2[0], v1[1] * v2[1], v1[2] * v2[2]; return dp; } ``` * 緊接著看看執行時間有沒有改變 `Execution time of raytracing() : 2.235425 sec` 時間快了將近1s ## OpenMP / Pthread 彼此沒有相依的data就很自然會想到平行化,不過對於OpenMP與Pthread還沒有很了解,因此要先找找資料!! * note : [OpenMP](http://aaz-blogger.blogspot.tw/2011/03/openmp-parallel-construct.html) [Pthread]() * 首先,因為不知道我的電腦上的執行緒最多可以到幾個,因此先來寫個小程式順便熟悉OpenMP的用法 * 這是一個讓每個執行緒都印出一次 Hello!!的程式 ```c #include<stdio.h> #include<omp.h> //要先include OpenMP library int main() { #pragma omp parallel // parallel directive 的特殊敘述行 //block外的data為各個多執行緒所共享,block內部則各自使用 { printf("Hello!!\n"); } return 0; } ``` * 編譯指令`gcc -fopenmp xxx.c -o xxx`要加上`-fopenmp`才能順利執行 ``` Hello!! Hello!! Hello!! Hello!! ``` 由此執行結果可知 --> 我的電腦有4個執行緒 但如果我想要限制不要使用全部的執行緒,再編譯之後在多加一行`export OMP_NUM_THREADS=2`即代表我只要使用到其中兩個執行緒 --- 來開始找看看哪裡可以套用OpenMP優化 * 跟Loop Unrolling改的地方一樣,只是改成openMP * 唯一需要注意的,在dot_product()需稍做更動,因為彼此資料是不獨立的 ```c double dot_product(const double *v1, const double *v2) { double dp = 0.0; #omp parallel for reduction(+:dp) for (int i = 0; i < 3; ++i) { dp += v1[i] * v2[i]; } return dp; } ``` * remind : 編譯要多加 `-fopenmp` !! * 緊接著看看執行時間有沒有改變 `Execution time of raytracing() : 1.844765 sec` **結果快超多!!!! 快接近編譯器最佳化了!!繼續努力!!** 但是使用openMP多執行緒, cache miss反而增加到38.658 % ``` Performance counter stats for './raytracing' (10 runs): 124,333 cache-misses # 38.658 % of all cache refs ( +- 8.44% ) (66.47%) ``` >> 請詳細閱讀 [Effects of Multithreading on Cache Performance](http://web.engr.oregonstate.edu/~benl/Publications/Journals/IEEE_ToC99.pdf),不要跳著讀,你需要培養背景知識 [name=jserv] ## Macro 重新改一下math-toolkit.h 將原本的function重新改成Macro ``` #define DOT(a,b) ((a[0]*b[0])+(a[1]*b[1])+(a[2]*b[2])) #define SUB(a,b,out) ((out[0]=a[0]-b[0]),(out[1]=a[1]-b[1]),(out[2]=a[2]-b[2])) #define ADD(a,b,out) ((out[0]=a[0]+b[0]),(out[1]=a[1]+b[1]),(out[2]=a[2]+b[2])) #define MULV(a,b,out) ((out[0]=a[0]*b[0]),(out[1]=a[1]*b[1]),(out[2]=a[2]*b[2])) #define MULC(a,b,out) ((out[0]=a[0]*(b)),(out[1]=a[1]*(b)),(out[2]=a[2]*(b))) ``` 減少程式執行時,call function的時間,因此可以縮短執行時間 ``` Execution time of raytracing() : 1.639391 sec ``` --- 優化到現在,再用一次gprof來分析程式 ``` % cumulative self self total time seconds seconds calls s/call s/call name 24.57 0.28 0.28 13861875 0.00 0.00 rayRectangularIntersection 20.19 0.51 0.23 10598450 0.00 0.00 normalize 12.73 0.66 0.15 13861875 0.00 0.00 raySphereIntersection 10.53 0.78 0.12 17821809 0.00 0.00 cross_product 8.78 0.88 0.10 4221152 0.00 0.00 multiply_vectors 6.14 0.95 0.07 2110576 0.00 0.00 localColor 3.51 0.99 0.04 4620625 0.00 0.00 ray_hit_object 3.51 1.03 0.04 1048576 0.00 0.00 ray_color 2.63 1.06 0.03 2110576 0.00 0.00 compute_specular_diffuse 2.19 1.08 0.03 2520791 0.00 0.00 idx_stack_top 1.76 1.10 0.02 1048576 0.00 0.00 rayConstruction 0.88 1.11 0.01 1241598 0.00 0.00 protect_color_overflow 0.88 1.12 0.01 1204003 0.00 0.00 idx_stack_push 0.88 1.13 0.01 1048576 0.00 0.00 idx_stack_init 0.88 1.14 0.01 1 0.01 1.14 raytracing 0.00 1.14 0.00 3838091 0.00 0.00 length 0.00 1.14 0.00 2558386 0.00 0.00 idx_stack_empty 0.00 1.14 0.00 1241598 0.00 0.00 reflection ``` 可以看的出來,原本定義再math-toolkit的function都不是排名前幾名了,接著就是想辦法再減少現在當今最消耗時間的部份!! --- ## 參考資料 * [功課介紹video](https://www.youtube.com/watch?v=m1RmfOfSwno) * [aaz的記憶倉庫-OpenMP](http://aaz-blogger.blogspot.tw/2011/03/openmp-parallel-construct.html) * [Effects of Multithreading on Cache Performance](http://web.engr.oregonstate.edu/~benl/Publications/Journals/IEEE_ToC99.pdf)