# 2017q1 Homework1 (raytracing)
contributed by <`csielee`>
### Reviewed by `yanang`
* 關於 thread vs openmp,可以參考 [去年的共筆整理](https://embedded2016.hackpad.com/ep/pad/static/f5CCUGMQ4Kp) 最後一段所展示的 thread 與 openmp 在四核處理器下共同使用將會競爭使用核心,導致效能降低。
```
THREAD OPENMP
# Rendering scene
Done!
Execution time of raytracing() : 1.791940 sec
POSIX THREAD
# Rendering scene
Done!
Execution time of raytracing() : 1.636069 sec
OPENMP
# Rendering scene
Done!
Execution time of raytracing() : 1.656069 sec
```
* 另外將各種方式 ( LOOP-UNROOLING 、Force Inline、MACRO、Remove Assert ) 搭配 thread 和 openmp ,所得出的最佳結果。

* 以及最後將 -Ofast 也加入使用的最佳化結果。
```
# Rendering scene
Done!
Execution time of raytracing() : 0.268195 sec
```
## 開發環境
OS: ubuntu 16.04 LTS
Architecture: x86_64
CPU 作業模式: 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
Model name: Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
CPU MHz: 2500.109
CPU max MHz: 2700.0000
CPU min MHz: 500.0000
L1d 快取: 32K, 8-way Set-associative
L1i 快取: 32K, 8-way Set-associative
L2 快取: 256K, 8-way Set-associative
L3 快取: 3072K, 12-way Set-associative
記憶體: 8G
## 嘗試運作未優化版本
```
./raytracing
# Rendering scene
Done!
Execution time of raytracing() : 6.660908 sec
```
正確得到圖形

## 利用gprof找出效能瓶頸
在編譯和連結時加上 `-pg` ,可以產生 `gmon.out` 讓gprof可以分析
執行 `$gprof -b raytracing gmon.out`
可以得到很多分析資料
```
% cumulative self self total
time seconds seconds calls s/call s/call name
23.44 0.52 0.52 69646433 0.00 0.00 dot_product
20.73 0.98 0.46 56956357 0.00 0.00 subtract_vector
11.49 1.24 0.26 31410180 0.00 0.00 multiply_vector
7.21 1.40 0.16 4620625 0.00 0.00 ray_hit_object
6.76 1.55 0.15 17836094 0.00 0.00 add_vector
6.76 1.70 0.15 13861875 0.00 0.00 raySphereIntersection
6.31 1.84 0.14 17821809 0.00 0.00 cross_product
4.51 1.94 0.10 10598450 0.00 0.00 normalize
4.51 2.04 0.10 1048576 0.00 0.00 ray_color
3.61 2.12 0.08 13861875 0.00 0.00 rayRectangularIntersection
1.80 2.16 0.04 1 0.04 2.22 raytracing
1.13 2.18 0.03 4221152 0.00 0.00 multiply_vectors
```
從上面的分析得知,花費時間最多的是 `dot_product` 這個函數
佔據了23.44%的時間
`subtract_vector` `multiply_vector` 也花費不少
從這些地方下手,預期能夠有效改善效能
## 利用gprof2dot讓graphviz產生圖
參考[hugikun999共筆](https://hackmd.io/s/HyHhgcv6#)得知,可以利用這些工具得到函數呼叫的路徑圖
先安裝
```
$sudo apt install python python-pip
$sudo pip install --upgrade pip
$sudo pip install gprof2dot
```
後執行
```
$gprof ./raytracing | gprof2dot | dot -T png -o output.png
```
就能得到

真是太強大了,不禁覺得佩服
## 使用loop unrolling
藉由gprof,發現在 `math-toolkit.h` 的很多熱點函數都有簡單的 for loop 存在
藉由展開迴圈可以減少 branch 執行預測上的損失,以及還要多讀取一個變數的記憶體時間
所謂的 **空間換取時間**
```clike
static inline
void add_vector(const double *a, const double *b, double *out)
{
out[0] = a[0] + b[0];
out[1] = a[1] + b[1];
out[2] = a[2] + b[2];
}
static inline
void subtract_vector(const double *a, const double *b, double *out)
{
out[0] = a[0] - b[0];
out[1] = a[1] - b[1];
out[2] = a[2] - b[2];
}
static inline
void multiply_vectors(const double *a, const double *b, double *out)
{
out[0] = a[0] * b[0];
out[1] = a[1] * b[1];
out[2] = a[2] * b[2];
}
static inline
void multiply_vector(const double *a, double b, double *out)
{
out[0] = a[0] * b;
out[1] = a[1] * b;
out[2] = a[2] * b;
}
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]*v2[2];
return dp;
}
```
展開後嘗試執行,時間縮短到 `5.705232 sec`
```
# Rendering scene
Done!
Execution time of raytracing() : 5.705232 sec
```
## 使用POSIX thread
參考[yanang共筆](https://hackmd.io/s/H1caWe7cx#)
在不更動 `main.c` 的情況,並利用前置處理加入Pthread版本
### 編譯的失誤
使用 `$make pthread` 編譯
```
cc -DIMPL="\"raytracing_pthread.h\"" -o raytracing_pthread objects.o raytracing.o main.o -lm -lpthread
```
執行後的時間
```
./raytracing_pthread
# Rendering scene
Done!
Execution time of raytracing() : 5.199946 sec
```
:::danger
這裡有很大的問題,是編譯的方法錯了
因為我是用 前置處理器去加入pthread的版本,所以編譯的時候如果拿已經轉成 .o 檔的檔案來linker是不會有我寫的code進去
原因在下面的圖
:::
```flow
st=>start: gcc編譯 .c 檔
e=>end: 得到執行檔
h=>operation: 標頭檔(.h)
op1=>operation: 前置處理器(preprocesser)
處理 # 符號
op2=>operation: 編譯器(compiler)
產生 .s 檔
op3=>operation: 組譯器(assembler)
產生 .o 檔
op4=>operation: 連結器(linker)
連結 .o 檔 生成 執行檔
st->op1->op2->op3->op4->e
```
因此如果我利用前置處理器去判斷是否要使用pthread版本
必須重新編譯 .c 檔產生 .o 檔
不能直接拿舊 .o 檔直接編譯
---
### 重新寫過之後
使用 `$make pthread` 編譯
```
cc -DPTHREAD=1 -o raytracing_pthread objects.o raytracing.c main.o -lm -lpthread
```
此時出現一些pthread使用上的bug
我才確定我真的有在編譯新的pthread版本
de掉這些bug後重新執行
```
./raytracing_pthread
# Rendering scene
Done!
Execution time of raytracing() : 0.941404 sec
```
天阿!這才是pthread的威力阿
## 使用openMP
參考[ryanwang522共筆](https://hackmd.io/s/BJsz9RZqx#)
加入openMP
### 編譯的錯誤
使用 `$make openmp` 編譯
```
cc -DOMP=1 -o raytracing_openmp objects.o raytracing.o main.o -lm -fopenmp
```
執行後的時間
```
./raytracing_openmp
# Rendering scene
Done!
Execution time of raytracing() : 5.023935 sec
```
:::danger
也是編譯上的問題,因此時間才沒有變化很大
:::
### 重新寫過
重新編譯並執行
```
make openmp
cc -DOMP=1 -o raytracing_openmp objects.o main.o raytracing.c -lm -fopenmp
./raytracing_openmp
# Rendering scene
Done!
Execution time of raytracing() : 1.045033 sec
```
時間上感覺比pthread來的久
因為openMP預設排程模式是 `auto` 的原因
把openMP的排程設定調整一下,用成一樣的模式 `schedule(static, 1)`
因為我們只有平行最外層的for,因此設定成1就可以跟pthread是一樣的分工模式
時間也可以差不多
```
./raytracing_openmp
# Rendering scene
Done!
Execution time of raytracing() : 0.940617 sec
```
太棒惹,輕鬆完成雙版本
## openMP V.S. Pthread
看到很多共筆都將平行化使用在raytracing的函數當中的for迴圈
就是將圖分程不同區塊讓不同執行緒運算,讓整張圖能夠平行且並行的進行預算
因為是使用執行緒實現並行的運算,所以可以有效能上的增進
但令我好奇的是,有 **openMP** 跟 **Pthread** 兩套API
究竟是哪一個好,或是各有優缺?
---
影響的可能性如下
* 執行處理器的核心數
* 能夠創建的執行緒數量
* 呼叫API跟使用編譯指示是否有差別
* 執行緒的排程
而不變的條件如下
* 每個pixel運算個別獨立
* 需使用私有變數
* 需使用共同變數
---
### 兩者變數的設定使用
在 **Pthread** 的使用上,因為只能夠傳入一個指標
因此如果有多個變數要傳入就需要另外設計一個結構去儲存
而且要使用共同變數就要宣告全域變數
而 **openMP** 的使用上,只有私有變數需要進行設定
其他已經宣告的變數都會變成全域變數
**個人評論**
> 我覺得openMP在這部份非常的好用
> 因為不用額外多加很多變數的宣告跟賦值
> 就能完成變數的設定[name=東霖]
### 呼叫API跟使用編譯指示
要使用 **Pthread** 需要去呼叫其API
而如果該台裝置沒有支援的函式庫就會無法進行編譯
並且使用執行緒需要額外寫函數給執行緒呼叫
**openMP** 則是利用 `#pragma` 的編譯指示
指示編譯器在編譯的時候需要額外做些事情
藉由這個如果編譯器可以了解要做的事情,就會去執行(幫你寫好code放進去)
如果不了解也不會失敗,頂多沒有執行
**個人評論**
> 老實說,我在寫openMP版本花不到幾分鐘
> 但Pthread卻寫了一陣子還出現bug
> 我認為如果沒有需要特別操作執行緒的行為
> 其實openMP就很好用
> 而且時間就是金錢,又快又有效[name=東霖]
### 執行緒的排程
在 **Pthread** 需要自己去設計執行緒的排程
並考慮實作方法
而 **openMP** 則不需要,可以利用schedule去設定排程
**個人評論**
> 有設計好的排程當然好
> 但也未必就不用思考本身平行化的排程選擇
> 在openMP預設的排程模式(auto)
> 發揮的效能比設計過的排程(cyclic)還要差
> 這邊就顯示出不管工具如何
> 開發者都要足夠了解要優化效能的特性[name=東霖]
### 小小總結
用個比喻來結束這個總結
:::success
Pthread就像沒組好的機器,要自己組裝
openMP就像組好的機器,只要會用就好
:::
## 參考資料
[hugikun999共筆](https://hackmd.io/s/HyHhgcv6#)
[高性能计算的线程模型:Pthreads 还是 OpenMP?](https://software.intel.com/zh-cn/articles/threading-models-for-high-performance-computing-pthreads-or-openmp)
[yangang共筆](https://hackmd.io/s/H1caWe7cx#)
[ryanwang522共筆](https://hackmd.io/s/BJsz9RZqx#)