# 2017q1 Homework1 (raytracing) contributed by <`ierosodin`> [github](https://github.com/ierosodin/raytracing) ### Reviewed by `janetwei` * 可以證明跟解釋爲何執行緒數量的結果差距 * 用 SIMD 來加速整個資料的處理 ## 開發環境 作業系統 : elementary OS loki `$ lscpu` Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 12 On-line CPU(s) list: 0-11 Thread(s) per core: 2 Core(s) per socket: 6 Socket(s): 1 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 45 Model name: Genuine Intel(R) CPU @ 3.30GHz Stepping: 5 CPU MHz: 2101.558 CPU max MHz: 3900.0000 CPU min MHz: 1200.0000 BogoMIPS: 6600.00 Virtualization: VT-x L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 15360K NUMA node0 CPU(s): 0-11 ## 軟體安裝 ``` $ sudo apt-get update $ sudo apt-get install graphviz $ sudo apt-get install imagemagick ``` 未優化前執行時間: ``` Execution time of raytracing() : 2.961364 sec ``` ## 使用gprof分析 ``` $ make PROFILE=1 $ ./raytracing ``` 使用 gprof 時, 執行時間會變長 ( gprof 使 gcc 在每個函数中都加入了一個 mcount ,也就是說每個函數都會調用 mcount , 增加執行時間) `Execution time of raytracing() : 5.704768 sec` ## Using gprof2dot gprof2dot 能將 gprof 或 perf 的分析結果轉成 dot 格式,裡面會描述各個節點間的關係 ``` $ git clone https://github.com/jrfonseca/gprof2dot $ gprof raytracing| ../gprof2dot/gprof2dot.py | dot -Tpng -o dot.png ``` ![](https://i.imgur.com/vii3IPj.png) ### 開始分析 `$ gprof -b raytracing gmon.out | less` ``` Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 22.82 0.52 0.52 69646433 0.00 0.00 dot_product 19.75 0.97 0.45 56956357 0.00 0.00 subtract_vector 10.97 1.22 0.25 10598450 0.00 0.00 normalize 9.66 1.44 0.22 13861875 0.00 0.00 rayRectangularIntersection 9.44 1.66 0.22 31410180 0.00 0.00 multiply_vector 7.02 1.82 0.16 13861875 0.00 0.00 raySphereIntersection 4.39 1.92 0.10 17821809 0.00 0.00 cross_product 3.73 2.00 0.09 17836094 0.00 0.00 add_vector 2.19 2.05 0.05 4620625 0.00 0.00 ray_hit_object 2.19 2.10 0.05 1048576 0.00 0.00 ray_color 1.54 2.14 0.04 4221152 0.00 0.00 multiply_vectors 1.32 2.17 0.03 1 0.03 2.27 raytracing 1.10 2.19 0.03 2520791 0.00 0.00 idx_stack_top 0.88 2.21 0.02 1241598 0.00 0.00 protect_color_overflow 0.88 2.23 0.02 1048576 0.00 0.00 rayConstruction 0.66 2.25 0.02 3838091 0.00 0.00 length 0.44 2.26 0.01 2110576 0.00 0.00 compute_specular_diffuse 0.44 2.27 0.01 2110576 0.00 0.00 localColor 0.44 2.28 0.01 1 0.01 0.01 delete_sphere_list 0.22 2.28 0.01 37595 0.00 0.00 idx_stack_pop 0.00 2.28 0.00 2558386 0.00 0.00 idx_stack_empty 0.00 2.28 0.00 1241598 0.00 0.00 reflection 0.00 2.28 0.00 1241598 0.00 0.00 refraction 0.00 2.28 0.00 1204003 0.00 0.00 idx_stack_push 0.00 2.28 0.00 1048576 0.00 0.00 idx_stack_init 0.00 2.28 0.00 113297 0.00 0.00 fresnel 0.00 2.28 0.00 3 0.00 0.00 append_rectangular 0.00 2.28 0.00 3 0.00 0.00 append_sphere ``` 從 gprof 調用表可以發現, dot_product 與 subtract_vector 被呼叫次數最多, 嘗試針對這兩項進行優化。 * baseline.ppm ![](https://i.imgur.com/Ht1ljuU.jpg) ## 優化 先試試 compiler 的優化能力吧!(目標超越它) ![](https://i.imgur.com/qDdEZhV.png) ### loop unrolling 可以發現, math-toolkit.h 是一個小迴圈,試著將這些 for 迴圈打開: ```clike= static inline double dot_product(const double *v1, const double *v2) { double dp = 0.0; dp += v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v1[2]; return dp; } ``` 再用 gprof 檢查一次,數學運算的部份所花費的時間下降了! ``` Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 16.90 0.25 0.25 10598450 0.00 0.00 normalize 12.85 0.44 0.19 69646433 0.00 0.00 dot_product 10.82 0.60 0.16 13861875 0.00 0.00 rayRectangularIntersection 10.48 0.76 0.16 56956357 0.00 0.00 subtract_vector 10.14 0.91 0.15 4620625 0.00 0.00 ray_hit_object ``` * gprof2dot ![](https://i.imgur.com/phyiuzm.png) 執行時間: `Execution time of raytracing() : 2.252323 sec` ![](https://i.imgur.com/IKGEKh8.png) ### force inline inline function 只能建議編譯器,也就是說建議並不一定會被採納,可以嘗試使用 `__attribute__((always_inline))` 執行時間: `Execution time of raytracing() : 1.979259 sec` ![](https://i.imgur.com/zbtPimO.png) ### 平行化 #### OpenMP 發現rayRectangularIntersection與raySphereIntersection對程式效能有影響, 嘗試對raytracing.c進行優化。 ``` #pragma omp parallel default (shared) private(stk, d, object_color) num_threads(omp_get_max_threads()) #pragma omp for schedule(static) ``` 使用 OpenMP 時要注意變數的性質,如果是每個 thread 都各自擁有的變數,須設為 privated。 可以用 diff 來檢查結果( out.ppm )是否正確 `$ diff baseline.ppm out.ppm` * out.ppm ![](https://i.imgur.com/bAasXZ3.jpg) 使用 OpenMP 對效能有很明顯的提升 `Execution time of raytracing() : 0.398234 sec` * gprof2dot ![](https://i.imgur.com/iKnLhe2.png) ![](https://i.imgur.com/rVNmCSd.png) >光是使用 OpenMP 就超越 -Ofast 拉~~ [name=ierosodin][color=green] #### pThreads 另一種平行化的方式,可以決定將某個 function 交給 thread 去做。 * main.c ++宣告 thread id ,並分配記憶體++ ```clike pthread_t *id = (pthread_t *) malloc(THREADNUM * sizeof(pthread_t)); ``` ++宣告一個指標型態的陣列,使每一個 id 都有一個指標型態的參數( ptr[i] ),用來傳入 function ++ ```clike rays **ptr = (rays **) malloc(THREADNUM * sizeof(rays *)); ``` ++建立 thread ,參數分別是: ( thread id , 屬性( NULL ), 要平行化的 function , 傳入的參數)++ ```clike pthread_create(&id[i], NULL, (void *) &raytracing, (void *) ptr[i]); ``` ++用來等待該 id 的 thread 結束,第二個參數用來儲存回傳值++ ```clike pthread_join(id[i], NULL); ``` * raytracing.c ++放在 raytracing() 的最後,表示該 function 結束後, thread 關閉++ ```clike pthread_exit(0); ``` 在 THREAD_NUM = 12 的情況下,執行時間縮短到 0.315882 秒! * gporf2dot ![](https://i.imgur.com/gS78lqZ.png) ![](https://i.imgur.com/KWaBDai.png) * 不同 THREAD_NUM 對效能的影響 ![](https://i.imgur.com/2yZlduD.png) 可以發現無止境的增加 THREAD_NUM 並不會對效能有明顯的影響,甚至會增加執行時間。 #### threadpool threadpool 不同於一般的 thread ,是將所有的 tasks 丟入 pool 中,而閒置的 thread 則從這個 pool 中取出可以執行的 task ,這樣可以避免不同 thread 之間工作量不平均或是 thread 執行速度不一而造成效能的損耗。 參考 [mergesort-concurrent](https://github.com/sysprog21/mergesort-concurrent) 中對 threadpool 的應用,實作在 raytracing 中,從效能比較圖可以發現, threadpool 比一般的 pthread 又更來得快速。 ![](https://i.imgur.com/IurkkBR.png) * 不同 THREAD_NUM 對效能的影響 | thread number | execution time | |:------:|:------:| |1|1.769772s| |2|0.867196s| |4|0.468847s| |8|0.302147s| |12|0.257031s| |16|0.258006s| |64|0.261613s| |128|0.262480s| |256|0.266355s| |512|0.282510s| ![](https://i.imgur.com/I2WDiAT.png) ### OpenCL https://www.fixstars.com/en/opencl/book/OpenCLProgrammingBook/opencl-c/ http://www.kimicat.com/opencl-1/opencl-jiao-xue-yi https://github.com/smistad/OpenCL-Getting-Started ### GCC optimization 最後,再用一次 -Ofast 吧~(不過要檢查最後出來的 out.ppm 是否正確,優化 pthread 可能會產生錯誤) ## 參考資料 [gprof2dot github](https://github.com/jrfonseca/gprof2dot) [pThreads for Raytracing](https://embedded2016.hackpad.com/ep/pad/static/wOu40KzMaIP) [Enhance raytracing program](https://embedded2016.hackpad.com/ep/pad/static/f5CCUGMQ4Kp)