# 2017q1 Homework1 (raytracing) contributed by < `twzjwang` > 作業說明: [B02: raytracing](https://hackmd.io/s/HyuBWDwYl) github: [twzjwang/raytracing](https://github.com/twzjwang/raytracing) ## Reviewed by `Sean1127` 1. 在 git commit message 不需要寫`Modify xxx.c`因為 GitHub 會自動追蹤被修改過的檔案 ## Reviewed by `Billy4195` * **"發現切割的範圍(h_start~h_end) h_end 設錯,每個 thread 都執行到 height"** 的程式碼可以強調修改的地方,這樣看一段code有點辛苦。 * raytracing()中的thread number可以用處理器數量來決定,讓程式碼有擴充性。 * [commit c268ef](https://github.com/twzjwang/raytracing/commit/c268ef030192b41b9b73fd787535638b5c11788e)的Subject超過50字,內容被隱藏在...裡面 # 開發環境 - Ubuntu Ubuntu 16.04.2 LTS Linux 4.8.0-36-generic - cpu version: Intel(R) Core(TM) i5-3337U CPU @ 1.80GHz 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 - memory size: 4GiB # 開發前 - 於 [2017q1 Homework1 (phonebook)](https://hackmd.io/BwUwzAZgjArARgFgLQBMwIOxIQqBDJYMEAkGANmACYqVyBODEEIA?view) `jserv` 提到 Ubuntu 14.04 版本太舊 - 因為專題使用的套件僅支援 Ubuntu 14.04 LTS 保留 Ubuntu 14.04 LTS 再另外灌 Ubuntu 16.04 LTS - 灌完後 grub 選單沒有第三個 OS - 參考 [Ubuntu重建GRUB開機選單](http://blog.752club.com/ubuntu-rebuild-grub-boot-menu/)解決 - 安裝開發工具 - graphviz - imagemagick - 第一次看到在 `main()` 裡使用 `#include` 的寫法 - call graph # 未優化版本 - 直接測試 - 編譯+執行 ``` $ make $ ./raytracing ``` - 結果 ``` # Rendering scene Done! Execution time of raytracing() : 3.388795 sec ``` - 使用gprof分析 - 編譯+執行 ``` $ make clean $ make PROFILE=1 $ ./raytracing $ gprof ./raytracing | less ``` - 結果 - 應該對呼叫次數最多的前幾個 function 做最佳化 ``` # Rendering scene Done! Execution time of raytracing() : 7.183814 sec ``` ``` Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 21.41 0.54 0.54 69646433 0.00 0.00 dot_product 17.61 0.98 0.44 56956357 0.00 0.00 subtract_vector 9.61 1.22 0.24 13861875 0.00 0.00 rayRectangularIntersection 8.81 1.44 0.22 13861875 0.00 0.00 raySphereIntersection 8.41 1.65 0.21 10598450 0.00 0.00 normalize ... ``` # 實驗一 - loop unrolling - 在[作業要求](https://hackmd.io/s/HyuBWDwYl)中提到幾個技巧中,這是唯一沒聽過的名詞,所以想從這個方法開始 - 先查了資料 - 循環展開,英文中稱(Loop unwinding或loop unrolling),是一種犧牲程序的尺寸來加快程序的執行速度的優化方法。可以由程式設計師完成,也可由編譯器自動優化完成。 ([Loop unrolling-wiki](https://en.wikipedia.org/wiki/Loop_unrolling)) - 將 `math-toolkit.h` 中所有 function 的 loop unroll - 如 `dot_product` : ```C static inline 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; } ``` unroll => ```C static inline 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; } ``` - 結果 - Execution time 7.183814 sec => 6.414793 sec (減為 89%) - call 最多次的 `dot_product` - self seconds 0.54 => 0.42 (減為 78%) ``` # Rendering scene Done! Execution time of raytracing() : 6.414793 sec ``` ``` Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 22.94 0.42 0.42 69646433 0.00 0.00 dot_product 11.89 0.63 0.22 56956357 0.00 0.00 subtract_vector 11.61 0.84 0.21 13861875 0.00 0.00 rayRectangularIntersection 11.06 1.04 0.20 10598450 0.00 0.00 normalize 8.85 1.20 0.16 17821809 0.00 0.00 cross_product ... ``` # 實驗二 - 使用 Pthread 平行化程式 - 在 `多處理機與平行程式設計` 課程曾使用過以下 API - MPI - distributed memory => 傳遞訊息 - cluster computer - 只有一台電腦所以暫時不考慮用MPI實作 - Pthread(POSIX Threads) - shared memory - OpenMP - shared memory - 使用指令就可平行化,但不知如何實作,容易出錯 - partitioning option ![](https://i.imgur.com/eirw6F2.png) (截自`多處理機與平行程式設計` 課程投影片ch3) ## 方法一 - block partition - 延續實驗一 - 選擇平行化的部分(function) - 如果資料互相獨立,不需要處理 critical section - 選擇重複執行相同指令、不同資料的部分,如:迴圈 - 選擇平行化 `raytracing()` 內的最外圍迴圈( `height` ) - 先用4個 threads - 傳遞參數部分寫法有點忘了 - 參考 [Multiple arguments to function called by pthread_create()?](http://stackoverflow.com/questions/1352749/multiple-arguments-to-function-called-by-pthread-create) - 編譯失敗 - Makefile 忘記加上 `-pthread` - 參考 [undefined reference to pthread_create in Linux](http://stackoverflow.com/questions/1662909/undefined-reference-to-pthread-create-in-linux) - 過程中不小心下到 `git reset` 到上一次commit - 原本以為要重寫... - 參考 [执行了git reset,还有办法取消吗?](https://segmentfault.com/q/1010000000167491)解決 - 一開始輸出的圖如下 ![](https://i.imgur.com/v9eHzPk.png) - 把`height` `width` `myrank` `threat_count` 以外的參數傳遞方法從 call by value => call by address - `make check` : `Verified OK` ![](https://i.imgur.com/I7uNHAm.png) - 但時間沒有明顯變快!? ``` $ ./raytracing # Rendering scene arg myrank 1 arg myrank 0 arg myrank 2 arg myrank 3 Done! Execution time of raytracing() : 3.450752 sec ``` - 發現切割的範圍(`h_start`~`h_end`) `h_end` 設錯,每個 thread 都執行到 `height` ```C int h_size = arg->height / arg->thread_count; int h_start = h_size * arg->myrank; int h_end = (arg->myrank == arg->thread_count - 1) ? h_size * (arg->myrank + 1) : arg->height; for (int j = h_start; j < h_end; j++) { ... ``` - 修正如下 ```C int h_size = arg->height / arg->thread_count; int h_start = h_size * arg->myrank; int h_end = (arg->myrank < arg->thread_count - 1) ? h_size * (arg->myrank + 1) : arg->height; for (int j = h_start; j < h_end; j++) { ... ``` > 可以強調一下修改了哪裡,閱讀速度會比較快[name=Billy SU] - 結果 - Execution time 3.388795 sec => 1.216715 sec (減為 36%) ``` $ ./raytracing # Rendering scene arg myrank 1 arg myrank 0 arg myrank 3 arg myrank 2 Done! Execution time of raytracing() : 1.216715 sec ``` - 若改變thread數量? - thread越多 => 效率越低 (因為 create thread 所需時間變多) - Speedup = Time(serial) / Time(parallel) - Efficenfy = Speedup / number of thread num of thread | 1 | 2 | 4 | 8 | 16 --------------|---|---|---|---|--- Execution time|2.51|1.38|1.22|1.11|1.10 Speedup|1.00|1.82|2.06|2.26|2.28 Efficency|1.00|0.91|0.52|0.28|0.14 ![](https://i.imgur.com/F87QBCb.png) ## 方法二 - cyclic partition - [作業解說影片](https://www.youtube.com/watch?v=m1RmfOfSwno)中提到熱區不平均的問題 - 原本切法 - block partition - myrank = 0 的 thread 處理 : ![](https://i.imgur.com/FQ3MXzZ.png) - 改成 - cyclic partition ```C for (int j = arg->myrank; j < arg->height; j += arg->thread_count) ``` - myrank = 0 的 thread 處理 : ![](https://i.imgur.com/i6iwr1s.png) - 結果 (4 threads) - Execution time 1.216715 sec => 1.086578 sec (減為 89%) ``` $ ./raytracing # Rendering scene arg myrank 0 arg myrank 1 arg myrank 2 arg myrank 3 Done! Execution time of raytracing() : 1.086578 sec ``` # 參考資料 - [Ubuntu重建GRUB開機選單](http://blog.752club.com/ubuntu-rebuild-grub-boot-menu/) - [Loop unrolling-wiki](https://en.wikipedia.org/wiki/Loop_unrolling) - [Multiple arguments to function called by pthread_create()?](http://stackoverflow.com/questions/1352749/multiple-arguments-to-function-called-by-pthread-create) - [undefined reference to pthread_create in Linux](http://stackoverflow.com/questions/1662909/undefined-reference-to-pthread-create-in-linux) - [执行了git reset,还有办法取消吗?](https://segmentfault.com/q/1010000000167491) - [gnuplot 語法解說和示範](https://hackmd.io/s/Skwp-alOg) ###### tags: `twzjwang`