# 2017q1 Homework1 (raytracing)
contributed by <`chenweiii`>
>> 必須先承認我看不太懂 raytracing 的程式是如何寫的、其背後的原理是什麼,只能先盡量閱讀別人共筆,改善 math-toolkit.h 部分,及學習如何使用 pThread 改善 [raytracing.c] raytracing。[time=Mon, Feb 27, 2017 4:59 PM]
>> 剛剛才發現老師有附一位同學的共筆有解說 raytracing,繼續努力![time=Mon, Feb 27, 2017 5:00 PM]
### Reviewed by `SeanCCC`
* 請補上硬體資訊
## Intel® Pentium® CPU B960 支援之 SIMD instruction set
```
MMX instructions
SSE / Streaming SIMD Extensions
SSE2 / Streaming SIMD Extensions 2
SSE3 / Streaming SIMD Extensions 3
SSSE3 / Supplemental Streaming SIMD Extensions 3
SSE4 / SSE4.1 + SSE4.2 / Streaming SIMD Extensions 4
```
## 安裝 gprof2dot
```
$ git clone https://github.com/jrfonseca/gprof2dot
$ cd gprof2dot
$ python setup.py build
$ python setup.py install
```
## 觀察
### Original Execution time
| | 開 -pg |不開 -pg |
| ------ | ----------- | ------- |
| -O0 | 10.872s | 6.28s |
| -Ofast | 2.32s | 2.24s |
### 使用 perf 觀察 branch-misses
```
Samples: 24K of event 'branch-misses', Event count (approx.): 11348775
Overhead Command Shared Object Symbol
35.42% raytracing raytracing [.] rayRectangularIntersection
13.11% raytracing raytracing [.] ray_color
10.72% raytracing raytracing [.] ray_hit_object
7.88% raytracing raytracing [.] raySphereIntersection
5.71% raytracing raytracing [.] refraction
5.70% raytracing raytracing [.] subtract_vector
4.35% raytracing raytracing [.] compute_specular_diffuse
3.33% raytracing raytracing [.] idx_stack_top
2.89% raytracing raytracing [.] multiply_vector
2.69% raytracing raytracing [.] add_vector
1.77% raytracing raytracing [.] raytracing
1.17% raytracing raytracing [.] length
0.92% raytracing raytracing [.] dot_product
0.80% raytracing raytracing [.] multiply_vectors
0.78% raytracing libm-2.23.so [.] __pow_finite
0.76% raytracing raytracing [.] localColor
0.63% raytracing raytracing [.] idx_stack_init
```
### 使用 gprof 觀察
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
17.10 0.85 0.85 1241598 0.00 0.00 refraction
15.90 1.64 0.79 69646433 0.00 0.00 dot_product
11.27 2.20 0.56 31410184 0.00 0.00 multiply_vector
9.96 2.69 0.49 56956357 0.00 0.00 subtract_vector
6.94 3.04 0.34 10598450 0.00 0.00 normalize
6.44 3.36 0.32 4620625 0.00 0.00 ray_hit_object
6.04 3.66 0.30 13861875 0.00 0.00 rayRectangularIntersection
5.43 3.93 0.27 13861875 0.00 0.00 raySphereIntersection
4.53 4.16 0.23 17836094 0.00 0.00 add_vector
3.42 4.33 0.17 1048576 0.00 0.00 rayConstruction
2.82 4.46 0.14 17821809 0.00 0.00 cross_product
2.62 4.59 0.13 1048576 0.00 0.00 ray_color
1.61 4.67 0.08 4221152 0.00 0.00 multiply_vectors
1.31 4.74 0.07 3838091 0.00 0.00 length
1.01 4.79 0.05 2110576 0.00 0.00 compute_specular_diffuse
1.01 4.84 0.05 2110576 0.00 0.00 localColor
1.01 4.89 0.05 1 0.05 4.97 raytracin
```
可以看到 refraction 時間最多,觀察在 call graph 的點,卻只有一條連出去到 dot_product 的線,但明明程式裡面也有執行 multiply_vector, subtract_vector,再重看一次文字版的 call graph 確實有執行到,但可能比例偏低,gprof2dot 便沒有把這些線畫出來了。
### call graph
![](https://i.imgur.com/nTzjB2i.png)
### [raytracing.c] what's samples?
* MSAA (MultiSampling Anti-Aliasing)
* 試試看 SAMPLES = 1,每個形狀的邊邊感覺點凹凸不平的現象
![](https://i.imgur.com/F0aK47E.png)
### [raytracing.c] raytracing
* 觀察到 height, width 兩個迴圈的設計是一個個 row 計算 pixel,raytracing 此函數的 cache misses ratio 為 1.29%,我把改成一個個 column 計算後,cache misses ratio 增加至 3.54%。程式的設計一不小心便影響到執行的效能。
* 玩弄一下
* width / 2
![](https://i.imgur.com/0OaNcG6.png)
* height / 2
![](https://i.imgur.com/JyMAs7C.png)
* width / 2 && height / 2
![](https://i.imgur.com/pd2gGWJ.png)
* height increase by 5
![](https://i.imgur.com/NnCyBIS.png)
* height and width increase by 5
![](https://i.imgur.com/rAdPE5K.png)
* 小結:雖然不太清楚程式如何運作,但透過此實驗可得每個 pixel 應都是可以獨立計算。
### 改善方向
- [x] loop unrolling
- [x] force inline
- [x] pthread
- [ ] OpenMP (referenced: [HuangWenChen](https://hackmd.io/s/r1osNDuT))
- [ ] 練習用 gnuplot 製作各種圖
>> 想法:128bit 的 SIMD instruction,在一個 function 裡頂多能一次處理兩對 float 變數,剩下一對還是得自己運算。但如果累積兩個 function 的量便有六對 float 變數,可以一次執行三次的 SIMD instruction
## 改善方向一:loop unrolling
將 math-toolkit.h 裡有用到迴圈之 function 通通展開,效果良好。
### 使用 perf 觀察 branch-misses
```
Samples: 17K of event 'branch-misses', Event count (approx.): 6951451, Thread: raytracing(12042)
Overhead Command Shared Object Symbol ◆
27.00% raytracing raytracing [.] rayRectangularIntersection ▒
23.12% raytracing raytracing [.] ray_color ▒
15.55% raytracing raytracing [.] raySphereIntersection ▒
10.38% raytracing raytracing [.] ray_hit_object ▒
8.56% raytracing raytracing [.] refraction ▒
3.86% raytracing raytracing [.] compute_specular_diffuse ▒
3.08% raytracing raytracing [.] raytracing ▒
2.35% raytracing raytracing [.] idx_stack_top ▒
1.11% raytracing raytracing [.] idx_stack_init ▒
1.06% raytracing raytracing [.] multiply_vector ▒
0.65% raytracing libm-2.23.so [.] __pow ▒
0.61% raytracing raytracing [.] protect_color_overflow ▒
0.60% raytracing libm-2.23.so [.] __pow_finite ▒
0.60% raytracing raytracing [.] subtract_vector ▒
0.57% raytracing raytracing [.] dot_product
```
* math-toolkit.h 的數學函數其 branch-misses 成功降低,但其它 raytracing 之函數其比例隨之上升。
* [Question] 不太了解為何 multiply_vector, subtract_vector 也會有 branch-misses,明明沒有 loop, if-else。下面摘錄 'perf Annotate multiply vector' 內容,**不太了解為何 mov 可以有大比例的 branch-misses**。
```
│ static inline ▒
│ void multiply_vector(const double *a, double b, double *out) ▒
│ { ▒
│ push %ebp ▒
1.12 │ mov %esp,%ebp ▒
│ sub $0x8,%esp ▒
│ mov 0xc(%ebp),%eax ▒
│ mov %eax,-0x8(%ebp) ▒
0.56 │ mov 0x10(%ebp),%eax ▒
│ mov %eax,-0x4(%ebp) ▒
│ out[0] = a[0] * b; ▒
│ mov 0x8(%ebp),%eax ▒
│ fldl (%eax) ▒
0.56 │ fmull -0x8(%ebp) ▒
97.75 │ mov 0x14(%ebp),%eax ▒
│ fstpl (%eax)
```
### 使用 gprof 觀察
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
24.11 0.89 0.89 1241598 0.00 0.00 refraction
14.44 1.42 0.53 31410181 0.00 0.00 multiply_vector
11.44 1.83 0.42 13861875 0.00 0.00 rayRectangularIntersection
7.90 2.12 0.29 10598450 0.00 0.00 normalize
6.81 2.38 0.25 69646433 0.00 0.00 dot_product
6.13 2.60 0.23 56956357 0.00 0.00 subtract_vector
5.99 2.82 0.22 4620625 0.00 0.00 ray_hit_object
4.36 2.98 0.16 13861875 0.00 0.00 raySphereIntersection
3.13 3.10 0.12 1048576 0.00 0.00 ray_color
2.72 3.19 0.10 1048576 0.00 0.00 rayConstruction
2.32 3.28 0.09 17836094 0.00 0.00 add_vector
2.32 3.37 0.09 17821809 0.00 0.00 cross_product
1.23 3.41 0.04 1 0.04 3.67 raytracing
1.09 3.45 0.04 3838091 0.00 0.00 length
```
除 refraction 外,其它 function 時間都有下降。
### 成果
```
# Rendering scene
Done!
Execution time of raytracing() : 4.722194 sec
```
時間從 6.28s -> 4.72s,改善 25%。
<br>
## 改善方向二:force inline
將 math-toolkit.h 的每個 function 加上 __attribute\__((always_inline)) 強制 inline
### 使用 gprof 觀察
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
27.81 1.09 1.09 13861875 0.00 0.00 rayRectangularIntersection
23.72 2.02 0.93 1241598 0.00 0.00 refraction
13.27 2.54 0.52 13861875 0.00 0.00 raySphereIntersection
9.44 2.91 0.37 2110576 0.00 0.00 compute_specular_diffuse
7.65 3.21 0.30 4620625 0.00 0.00 ray_hit_object
7.14 3.49 0.28 1048576 0.00 0.00 ray_color
5.10 3.69 0.20 2110576 0.00 0.00 localColor
4.08 3.85 0.16 1048576 0.00 0.00 rayConstruction
0.77 3.88 0.03 1241598 0.00 0.00 reflection
0.77 3.91 0.03 1 0.03 3.92 raytracing
0.26 3.92 0.01 1241598 0.00 0.00 protect_color_overflow
```
因為強制 inline 的關係,math-toolkit.h 裡的數學函數便在 gprof 裡觀察不到了,也因此 raytracing.h 的各項函數時間因此上升。
### 成果
```
# Rendering scene
Done!
Execution time of raytracing() : 4.355438 sec
```
時間較上一個版本下降約 0.3s。
<br>
## 改善方向三:使用 pthread
### 先準備練習
參考材料:
* [POSIX Threads Programming](https://computing.llnl.gov/tutorials/pthreads/)
* [LanKuDot 共筆](https://hackmd.io/s/rkggUeKa)
### pthread 注意事項
* 一次只能傳一個參數,而且型態為 (void *),故須設計一個 struct,將所需資料傳入。
* 記得將 argument 轉型。
### 修改之內容
```clike=
/* raytracing.h */
typedef struct __RAYTRACING_ITEM {
double *u, *v, *w;
uint8_t *pixels;
double *background_color;
rectangular_node rectangulars;
sphere_node spheres;
light_node lights;
const viewpoint *view;
int factor;
int from_height, to_height;
int height;
int width;
} ritem;
```
* 值得注意的是 from_height, to_height,代表此 thread 所負責產生的 block。
* raytracing 修改為準備 thread 所需的 struct 內容並產生 thread。
* 新增 rayThread 做 raytracing 原本的工作。
### 成果
```
# Rendering scene
Done!
Execution time of raytracing() : 2.215274 sec
```
**從上個版本 4.355s -> 2.215s,改善 49%,比原始版本改善 65%,已比我的電腦開 -Ofast 還要來得好。**
### 不同 threads 個數之 Execution Time
![](https://i.imgur.com/gywcPYL.png)
<br>
## 改善方向四:使用 pthread 嘗試不同策略
### thread 主動競爭 row
>> 這邊是看到某一位同學所提出的想法,但很抱歉的是我把分頁關掉了,reference 之後再補上 orz [time=Wed, Mar 1, 2017 9:53 PM]
練習使用 pthread_mutex_t,從 [POSIX Threads Programming](https://computing.llnl.gov/tutorials/pthreads/) 看到的是將 mutex 宣告為全域變數,但這邊我宣告為 raytracing 的區域變數再傳入 rayThread。
```clike=
while (j < rayInfo->height) {
pthread_mutex_lock(rayInfo->_MUTEX);
j = ++(*rayInfo->__CURRENT_HEIGHT);
pthread_mutex_unlock(rayInfo->_MUTEX);
if (j >= rayInfo->height) break;
/* raytracing */
/* ... */
}
```
這邊也使用一個 __CURRENT_HEIGHT,代表 thread 目前所要做的 row。
```
# Rendering scene
Done!
Execution time of raytracing() : 2.272589 sec
```
時間表現沒有比較優異 orz
### cyclic partition
參考 [twzjwang](https://hackmd.io/s/BJkx6f6Fe) 的方法
```clike=
for (int j = rayInfo->from_height; j < rayInfo->height; j += NUM_THREADS)
```
稍微修改一下即可使用。
```
# Rendering scene
Done!
Execution time of raytracing() : 2.202453 sec
```
改善有限。