# 2017q1 Homework1(raytracing)
contributed by < `baimao8437` >
---
**Reviewed by `csielee`**
* 在使用 openMP 的地方可以考慮使用 openMP 的 schedule 功能,因為預設的 schedule 都是 auto,可能會遇到熱區不平衡的問題,可參考[ryanwang522共筆](https://hackmd.io/s/BJsz9RZqx#)
---
### 花費時間 ~2/25~ ~-~ ~2/28~
* 讀書:6 hr
* 文件:6 hr
* coding: 4 hr
* ~~看著發呆:3 hr~~
## 目標設定
* 習慣 linux 系統基本操作
* 熟練 git 基本版本控管操作
* 效能分析 gprof & 繪圖工具 gprof2dot 、 graphviz 練習
```
make PROFILE=1 && ./raytracing
gprof ./raytracing | ~/gprof2dot/gprof2dot.py| dot -Tpng -o callgraph.png
```
* HackMD 學習報告寫作練習
* 學習 GDB debug
* 減少發呆時間...
## 前置作業
1. 開發工具安裝
```
$ sudo apt-get update
$ sudo apt-get install graphviz
$ sudo apt-get install imagemagick
```
## 開發環境
```
baimao@baimao-Aspire-V5-573G:~$ lscpu
Architecture: x86_64
CPU 作業模式: 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
每核心執行緒數:2
每通訊端核心數:2
Socket(s): 1
NUMA 節點: 1
供應商識別號: GenuineIntel
CPU 家族: 6
型號: 69
Model name: Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz
製程: 1
CPU MHz: 1711.242
CPU max MHz: 2600.0000
CPU min MHz: 800.0000
BogoMIPS: 4589.38
虛擬: VT-x
L1d 快取: 32K
L1i 快取: 32K
L2 快取: 256K
L3 快取: 3072K
NUMA node0 CPU(s): 0-3
```
## 效能分析
測試未優化效能:
直接執行程式 結果為3.25 sec
```
# Rendering scene
Done!
Execution time of raytracing() : 3.252078 sec
```
```make clean``` 後使用 ```make PROFILE=1``` 編譯
執行結果為 6.88 sec
```
# Rendering scene
Done!
Execution time of raytracing() : 6.886804 sec
```
現在輸入 ```$ gprof ./raytracing | less``` 嘗試了解執行次數
```
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls Ts/call Ts/call name
% the percentage of the total running time of the
time program used by this function.
cumulative a running sum of the number of seconds accounted
seconds for by this function and those listed above it.
self the number of seconds accounted for by this
seconds function alone. This is the major sort for this
listing.
calls the number of times this function was invoked, if
this function is profiled, else blank.
self the average number of milliseconds spent in this
ms/call function per call, if this function is profiled,
else blank.
total the average number of milliseconds spent in this
ms/call function and its descendents per call, if this
function is profiled, else blank.
name the name of the function. This is the minor sort
for this listing. The index shows the location of
the function in the gprof listing. If the index is
in parenthesis it shows where it would appear in
the gprof listing if it were to be printed.
^L
Copyright (C) 2012-2016 Free Software Foundation, Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved.
```
怎麼跟我想像的完全不一樣 一開始就碰壁
:::success
已解決 gcc 版本 6.2.0 在 makefile 加入新的 flag ```-no-pie``` 可順利執行
:::
> 同學你好,我也遇到相同問題。請問你這問題有解決了嗎@@
> [name=yangyang95]
> 靠你解決的 感謝你~~
> [name=baimao8437]
順利執行 回頭補先前版本的 gprof
使用 ```$ git checkout <commit_id>``` 回復舊版
* gprof
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
23.03 0.61 0.61 69646433 0.00 0.00 dot_product
20.77 1.16 0.55 56956357 0.00 0.00 subtract_vector
11.89 1.48 0.32 31410180 0.00 0.00 multiply_vector
9.06 1.72 0.24 13861875 0.00 0.00 rayRectangularIntersection
7.55 1.92 0.20 10598450 0.00 0.00 normalize
6.04 2.08 0.16 13861875 0.00 0.00 raySphereIntersection
4.53 2.20 0.12 17836094 0.00 0.00 add_vector
3.78 2.30 0.10 4620625 0.00 0.00 ray_hit_object
3.40 2.39 0.09 17821809 0.00 0.00 cross_product
3.40 2.48 0.09 1048576 0.00 0.00 ray_color
0.94 2.50 0.03 4221152 0.00 0.00 multiply_vectors
0.76 2.52 0.02 3838091 0.00 0.00 length
0.76 2.54 0.02 2520791 0.00 0.00 idx_stack_top
0.76 2.56 0.02 2110576 0.00 0.00 compute_specular_diffuse
0.76 2.58 0.02 2110576 0.00 0.00 localColor
0.76 2.60 0.02 1048576 0.00 0.00 rayConstruction
0.38 2.61 0.01 1241598 0.00 0.00 protect_color_overflow
0.38 2.62 0.01 1241598 0.00 0.00 refraction
0.38 2.63 0.01 1204003 0.00 0.00 idx_stack_push
0.38 2.64 0.01 37595 0.00 0.00 idx_stack_pop
0.38 2.65 0.01 1 0.01 2.65 raytracing
0.00 2.65 0.00 2558386 0.00 0.00 idx_stack_empty
0.00 2.65 0.00 1241598 0.00 0.00 reflection
0.00 2.65 0.00 1048576 0.00 0.00 idx_stack_init
0.00 2.65 0.00 113297 0.00 0.00 fresnel
0.00 2.65 0.00 3 0.00 0.00 append_rectangular
0.00 2.65 0.00 3 0.00 0.00 append_sphere
0.00 2.65 0.00 2 0.00 0.00 append_light
0.00 2.65 0.00 1 0.00 0.00 calculateBasisVectors
0.00 2.65 0.00 1 0.00 0.00 delete_light_list
0.00 2.65 0.00 1 0.00 0.00 delete_rectangular_list
0.00 2.65 0.00 1 0.00 0.00 delete_sphere_list
0.00 2.65 0.00 1 0.00 0.00 diff_in_second
0.00 2.65 0.00 1 0.00 0.00 write_to_ppm
```
>Git : 由於我只是用 checkout 回到過去版本 修改了檔案
那我在 rebase 前 就必須先用 ```$ git stash save``` 暫存我剛剛的修改
再用 ```$ git rebase -i <commit_parent>``` 真正的回到過去修改
成功後用 ```$ git stash pop``` 將剛剛暫存的修改還原
最好是直接 rebase 再改 不過也學到 stash 怎麼用了
進入 rebase 後會讓你選擇 command 因為我是要修改檔案 所以選 edit
```
$ git rebase -i 3165ec8
Stopped at 694f6fd... Force all inline to always inline
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
```
>到這裡就可以開始修改程式碼 之後就 add 跟 commit --amend 了 好了之後再```$ git rebase --continue```
哇 原來改了一版 後面就自動改好了 以後只要 edit 第一個版本就好囉 蒸蚌
## 效能提升
### 強制開啟 inline
看完影片後 決定從最簡單的部份下手
在 ```math-toolkit.h``` 中 在每個 ```static inline``` 後面都加上 ```__attribute__((always_inline))```強制開啟 inline
#### 結果:
```
# Rendering scene
Done!
Execution time of raytracing() : 4.206481 sec
```
從 6.88 sec 降至 4.20 sec
又發現在 ```idx_stack.h``` 中 也有 inline 全部強制
#### 結果:
```
# Rendering scene
Done!
Execution time of raytracing() : 3.712347 sec
```
又順利下降了一些 變成 3.71 sec
### Loop unrolling
將 ```math-toolkit.h``` 中用到的 for loop 都解開
能夠避免多次的算術運算 有效降低時間
#### 結果:
```
# Rendering scene
Done!
Execution time of raytracing() : 2.701332 sec
```
由於避免了 for 所產生的 branch 效能提升從 3.71 sec 降至 2.70 sec
### Macro
做完後覺得 其實這些運算 也沒有複雜到一定要獨立成一個 function 嘛
如果能以直接計算取代 call function 必定可以節省時間
所以把 ```math-toolkit.h``` 的 ```dot_product、subtract_vector、multiply_vector...``` 全部換成以 macro 寫法直接進行運算
順便把兩個完全沒有用到的 function(scalar_triple_product & scalar_triple) 刪掉
#### 結果:
```
# Rendering scene
Done!
Execution time of raytracing() : 2.086252 sec
```
不出所料的降低時間 從 2.70 sec 變成 2.08 sec
但是...
![](https://i.imgur.com/SDG79t6.png)
圖形出錯了
使用 ```$ git checkout <commit_id>``` 回復版本檢查
後來找出兇手 是把 ```multiiply_vector``` 換成 macro 造成的 真詭異 我應該沒有換錯才對
這邊參考了前輩們的筆記 將
```
#define multiply_vector(a, b, out) (out[0] = a[0] * b, out[1] = a[1] * b, out[2] = a[2] * b)
```
換成
```
#define multiply_vector(a, b, out) (out[0] = a[0] * (b), out[1] = a[1] * (b), out[2] = a[2] * (b))
```
b有沒有括號原來有差@@ 終於回復原本圖形
> Macro 在compile時期會把字代換,所以沒加括號如果 * 優先緒高 就會出錯.
> ex 本來 a*(b+c) 少括號,a*b+c
> [name=purpon]
> 跪了跪了
> [name=baimao8437]
![](https://i.imgur.com/2zlZUdp.png)
* gprof
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
31.11 0.37 0.37 13861875 0.00 0.00 rayRectangularIntersection
22.70 0.64 0.27 2110576 0.00 0.00 compute_specular_diffuse
10.93 0.77 0.13 13861875 0.00 0.00 raySphereIntersection
10.51 0.90 0.13 1048576 0.00 0.00 ray_color
9.25 1.01 0.11 4620625 0.00 0.00 ray_hit_object
5.04 1.07 0.06 1048576 0.00 0.00 rayConstruction
3.36 1.11 0.04 2110576 0.00 0.00 localColor
3.36 1.15 0.04 1241598 0.00 0.00 reflection
1.68 1.17 0.02 1 0.02 1.19 raytracing
1.26 1.18 0.02 1241598 0.00 0.00 protect_color_overflow
0.84 1.19 0.01 1241598 0.00 0.00 refraction
0.00 1.19 0.00 113297 0.00 0.00 fresnel
0.00 1.19 0.00 3 0.00 0.00 append_rectangular
0.00 1.19 0.00 3 0.00 0.00 append_sphere
0.00 1.19 0.00 2 0.00 0.00 append_light
0.00 1.19 0.00 1 0.00 0.00 calculateBasisVectors
0.00 1.19 0.00 1 0.00 0.00 delete_light_list
0.00 1.19 0.00 1 0.00 0.00 delete_rectangular_list
0.00 1.19 0.00 1 0.00 0.00 delete_sphere_list
0.00 1.19 0.00 1 0.00 0.00 diff_in_second
0.00 1.19 0.00 1 0.00 0.00 write_to_ppm
```
### OpenMP
屬於比較簡易的程式平行化 只要 for 裡的內容獨立可以對 for 做平行化
在 ```raytracing.c``` 中 找到巢狀迴圈
在上一行加入 ```#pragma omp parallel for```
+```num_threads(64)``` -> 產生64個 thread 來平行處理
最後修改 ```Makefile``` 中的CFLAGS加上```-fopenmp``` LDFLAGS加上```-lgomp```
#### 結果
```
# Rendering scene
Done!
Execution time of raytracing() : 1.428162 sec
```
平行處理就是猛阿 簡單幾行就從 2.08 sec 又降了許多變 1.42 sec
but 圖形卻...
![](https://i.imgur.com/D4TVsco.png)
這邊參考前輩的筆記 發現我少做了一件事
因為在做平行化時 兩個執行續可能會同時修改到相同的變數
所以應該要把迴圈內會被修改到的變數全部加上 private
所以把會改變到的變數 : stk 、 d 、 object_color 這3個變數
以 private 包起來 加進平行化的參數
```#pragma omp parallel for num_threads(64) private(stk), private(d),private(object_color)```
然後圖又正常了
![](https://i.imgur.com/4xhY3XU.png)
* gprof
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
41.19 0.07 0.07 309853 0.00 0.00 rayRectangularIntersection
17.65 0.10 0.03 21578 0.00 0.01 ray_color
11.77 0.12 0.02 318752 0.00 0.00 raySphereIntersection
11.77 0.14 0.02 51328 0.00 0.00 localColor
11.77 0.16 0.02 48388 0.00 0.00 compute_specular_diffuse
5.88 0.17 0.01 27916 0.00 0.00 reflection
0.00 0.17 0.00 109628 0.00 0.00 ray_hit_object
0.00 0.17 0.00 28481 0.00 0.00 protect_color_overflow
0.00 0.17 0.00 26259 0.00 0.00 refraction
0.00 0.17 0.00 20410 0.00 0.00 rayConstruction
0.00 0.17 0.00 2935 0.00 0.00 fresnel
0.00 0.17 0.00 3 0.00 0.00 append_rectangular
0.00 0.17 0.00 3 0.00 0.00 append_sphere
0.00 0.17 0.00 2 0.00 0.00 append_light
0.00 0.17 0.00 1 0.00 0.00 calculateBasisVectors
0.00 0.17 0.00 1 0.00 0.00 delete_light_list
0.00 0.17 0.00 1 0.00 0.00 delete_rectangular_list
0.00 0.17 0.00 1 0.00 0.00 delete_sphere_list
0.00 0.17 0.00 1 0.00 0.00 diff_in_second
0.00 0.17 0.00 1 0.00 170.07 raytracing
0.00 0.17 0.00 1 0.00 0.00 write_to_ppm
```
可以看到平行化使前面幾名的執行時間明顯的降低了
### AVX
在後面的作業比較熟悉 AVX 後回來寫 就覺得很簡單了
針對 add_vector 先測試原本的效能
```
register __m256d ymm0, ymm1;
ymm0 = _mm256_set_pd(a[0],a[1],a[2],0.0);
ymm1 = _mm256_set_pd(b[0],b[1],b[2],0.0);
ymm1 = _mm256_add_pd(ymm0, ymm1);
double tmp[4] __attribute__((aligned(32)));
_mm256_store_pd(tmp, ymm1);
out[0] = tmp[3];
out[1] = tmp[2];
out[2] = tmp[1];
```
比較需要注意的是 out[0] = tmp[3] ; out[1] = tmp[2] ; out[2] = tmp[1];
#### 結果
還沒優化前效能
* 執行時間
```
# Rendering scene
Done!
Execution time of raytracing() : 2.965848 sec
```
* gprof
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
3.39 1.67 0.06 17836094 0.00 0.00 add_vector
```
再來看以 AVX 運算的結果
* 執行時間
```
# Rendering scene
Done!
Execution time of raytracing() : 4.582634 sec
```
哪尼!居然變久了
* gprof
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
5.68 2.41 0.16 17836094 0.00 0.00 add_vector
```
真的沒有比直接加快 來改看看比較複雜一點的 dot product
還沒優化前效能
* 執行時間
```
# Rendering scene
Done!
Execution time of raytracing() : 3.587229 sec
```
* gprof
```
% cumulative self self total
time seconds seconds calls s/call s/call name
20.41 0.76 0.36 69646433 0.00 0.00 dot_product
```
AVX 優化後結果
* 執行時間
```
# Rendering scene
Done!
Execution time of raytracing() : 6.080835 sec
```
恩...
* gprof
```
% cumulative self self total
time seconds seconds calls s/call s/call name
26.09 0.91 0.91 69646433 0.00 0.00 dot_product
```
看來 AVX 加速還是比較適用於一次處理 4 倍資料 用在這裡沒有比較快
## 參考資料:
* [JiJi大的筆記](https://hackmd.io/s/r1vckLB6)
* [gprof bug fix](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=854704)
* [Git rebase 教學](https://zlargon.gitbooks.io/git-tutorial/content/patch/rebase_interactive.html)
* [OpenMP private](https://kheresy.wordpress.com/2006/09/22/%E7%B0%A1%E6%98%93%E7%9A%84%E7%A8%8B%E5%BC%8F%E5%B9%B3%E8%A1%8C%E5%8C%96%EF%BC%8Dopenmp%EF%BC%88%E4%BA%94%EF%BC%89-%E8%AE%8A%E6%95%B8%E7%9A%84%E5%B9%B3%E8%A1%8C%E5%8C%96/)