# 2016q3 Homework2 (raytracing)
contributed by <`LanKuDot`>
## 環境
* OS: Ubuntu 15.04 64bit
* CPU: Genuine Intel® CPU U7300 @ 1.30GHz × 2
* GPU: Mobile Intel® GM45 Express Chipset
* RAM: 4 GB
## 程式碼
### Gprof
* `make PROFILE=1`:多加 `-pg` flag,讓 compiler 安插額外程式碼輸出執行時期的資料讓 `gprof` 分析,執行完會產生 `gmon.out`,以追蹤程式的執行行為 (動態分析)
```
[Makefile]
ifeq ($(strip $(PROFILE)),1) "strip 幫助去除 leading 或 trailing space
PROF_FLAGS = -pg
CFLAGS += $(PROF_FLAGS)
LDFLAGS += $(PROF_FLAGS)
endif
```
* `gprof ./raytracing`: 讓 gprof 分析資料,gprof 會取用產生的資料檔。會列出呼叫次數、執行時間比、call graph 等資訊。配合 `less` 服用,用分頁顯示大量資料。
### static inline
* `static`:對 definition 有效,限制 function 或 global variable 只會在所在檔案中被 access
* `lnline`:告訴編譯器將 function 直接展開成 definition 的程式,可透過加上 `__attribute__((always_line))` 強制 gcc 在最佳化沒有開啟時 inline。
### 核心
#### Function `raytracing`
* 傳入場景必要的物件:長方面 (牆)、球體、光源、背景色、攝影機位置及視角、攝影機畫面 (輸出圖片) 的長寬
* 先計算以攝影機平面為基準的 vector space 的 basis vector,將結果存在 vector `u`, `v`, `w`。
* **function `calculateBasisVector`**:
* `w`:攝影機面的 normal (vpn, view-plane normal)
* `u`:w x 攝影機面的 up vector (vup)
* `v`:w x u 找到最後一個 vector
* `SAMPLES`:為了反鋸齒 (MSAA,多重採樣反鋸齒法),設定成取樣四個 pixel 作為該 pixel 的顏色
* 取法如下
```
以 SAMPLES = 4 為例,對於 pixel (0,0) 會採
(0*2+0/2, 0*2+0%2) = (0,0)
(0*2+1/2, 0*2+1%2) = (0,1)
(0*2+2/2, 0*2+2%2) = (1,0)
(0*2+3/2, 0*2+3%2) = (1,1) 四個點做平均
而 pixel (1,0) 則為 (2,0), (2,1), (3,0), (3,1)四個點
每次採樣的點都不一樣,可以在這裡做平行化
```
* 從攝影機畫面的每個 pixel 射出光源
* **function `rayConstruction`**:建立光源出發點,會存在 `d`。
* 設定 view plane 的大小為 [-0.0175:0.0175][-0.0175:0.0175] (座標);消失點的位置在 z = 0.05 的地方 (view plane space)
* 計算該 pixel 在 view plane space 上的座標
* `w` (z):座標為消失點 z 座標
* `u` (x):為 xMin + xUnit * i
* `v` (y):為 yMin + yUnit * j
* 將得到的座標轉換到 world space 上:
```
basis of space B(represented by space A) * coordainate on space B + origin on space B(represented by space A)= coordinate on space A
```
* 後與 view plane 的原點相減(?)
* 計算光源在旅行的過程中會變成什麼顏色
* **function `rayColor`**
* 判定如果發出去的光沒有碰到任何東西,則加上背景顏色,如果有則加上旅行之後的顏色。(這裡的顏色 scale 為 [0.0:1.0])
* 將 4 個取樣點的顏色值做平均,map 到 [0:255],然後存到 `pixel` 中
### 輸出
* [ppm](http://netpbm.sourceforge.net/doc/ppm.html):`P6 Width Height MaxColorValue ColorValue`
* `ColorValue`:以 (row, column) 填值
## 優化
### Baseline
* 執行時間:11.650552 sec
* 時間大多卡在 `dot_product` 跟 `substract_vector`
```
前 10 名的程式熱區:
% cumulative self self total
time seconds seconds calls s/call s/call name
27.96 2.61 2.61 69646433 0.00 0.00 dot_product
18.32 4.32 1.71 56956357 0.00 0.00 subtract_vector
9.64 5.22 0.90 13861875 0.00 0.00 rayRectangularIntersection
6.75 5.85 0.63 31410180 0.00 0.00 multiply_vector
5.68 6.38 0.53 17836094 0.00 0.00 add_vector
5.68 6.91 0.53 10598450 0.00 0.00 normalize
5.46 7.42 0.51 4620625 0.00 0.00 ray_hit_object
4.18 7.81 0.39 13861875 0.00 0.00 raySphereIntersection
4.18 8.20 0.39 17821809 0.00 0.00 cross_product
3.05 8.49 0.29 1048576 0.00 0.00 ray_color
```
### Loop Unrolling
* 由於 for loop 有 branch 的判斷,會減慢執行速度。而在 `math_toolkit.h` 中的 vector 運算最多為三維,可以輕鬆展開
* 執行時間:6.881327 sec (效能提昇 41%)
* 可以看到做 loop unrolling 之後的函式總執行時間下降很多
```
dot_product 2.61 -> 0.73
substact_vector 1.71 -> 0.70
multiply_vector 0.63 -> 0.43
add_vector 0.53 -> 0.21 (seconds)
```
### 使用 pthread 做平行化處理
* 閱讀材料
- [ ] [POSIX Threads Programming](https://computing.llnl.gov/tutorials/pthreads/)
- [ ] [Introduction to Parallel progamming](https://computing.llnl.gov/tutorials/parallel_comp/)
* 我認為 pthread 可以用在
* 重複執行的動作,像是 for loop
* 可以分開的工作
* 如果沒有 critical section 會比較好處理,否則要注意共用資源的使用問題
* 觀察 raytracing 可以做平行化的地方
* 整張圖的運算切割:row 分, column 分或 block 分。缺點是老師在影片中講到的,每一個 pixel 的呼叫熱度不一樣,所以如果切出來熱度過於集中在某個 block,平行化的效果不高。
* 模糊化處理:每個 sample 做完運算後,再取平均。
* RGB:RGB 的計算結果是不互相干擾的,可以個別平行加。缺點 `ray_color` 計算完顏色一次輸出 RGB,如果每個 thread 只取其中一個值會太浪費。
#### 整張圖運算切割
* 基本想法:以 row 為單位切割運算區間,將 `raytracing` 的核心迴圈移到 `raytracing_thread` 函式,在 `raytracing` 只負責 create thread。
* 因為 `pthread_create` 只允許 pass 一個參數,所以將所需資訊打包成一個資料結構
```c
typedef struct __RAY_TRACING_THREAD_INFO {
int height;
int width;
int fromHeight; // Inclusive, divide the blocks by rows
int toHeight; // Exclusive
uint8_t *pixels; // The block of data for each thread is not overlapping.
color background_color;
rectangular_node rectangulars;
sphere_node spheres;
light_node lights;
const viewpoint *view;
} thread_info;
```
* 問題:運行時間雖然減少,但是輸出的圖總是少一塊

* 原因是因為 passing 的參數是 shared memory,而在我的資料結構中 `fromHeight`,`toHeight` 會因為迴圈而改變,所以在 thread 取用之前,就可能被更改了
* 驗證
```c=
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *printMessage(void *msg)
{
int *value;
sleep(1); // Wait for a while
value = (int *)msg;
printf("%d\n", *value);
pthread_exit((void *)0);
}
#define NUM_OF_THREADS 5
int main(void)
{
pthread_t threads[NUM_OF_THREADS];
void *status;
int x;
for (int i = 0; i < NUM_OF_THREADS; ++i) {
x = i;
pthread_create(&threads[i], NULL, printMessage, (void *)&x);
}
for (int i = 0; i < NUM_OF_THREADS; ++i)
pthread_join(threads[i], &status);
return 0;
}
```
```
Output:
4
4
4
4
4
```
* 解法:如果是 thread 間有差異的資料,用 array 存並 pass 對應的 element 到 thread 中。稍微修改一些地方:
```c=
int x[NUM_OF_THREADS];
for (int i = 0; i < NUM_OF_THREADS; ++i) {
x[i] = i;
pthread_create(&threads[i], NULL, printMessage, (void *)&x[i]);
}
```
```
Output:
1
0
2
3
4
```
* 所以我必須要為每一個 thread 準備一份他所需要的資料,不過對於同樣的資料可以用 pointer 指向同一個地方。修改過後的資料結構
```c
typedef struct __RAY_TRACING_THREAD_INFO {
int *pHeight;
int *pWidth;
int fromHeight; // Inclusive, divide the blocks by rows
int toHeight; // Exclusive
uint8_t *pixels; // The block of data for each thread is not overlapping.
color background_color;
rectangular_node *pRectangulars;
sphere_node *pSpheres;
light_node *pLights;
const viewpoint *view;
} thread_info;
```
* 執行時間:
* 3.664291 sec (4 threads)
* 3.602248 sec (8 threads)
* 3.608806 sec (16 threads)
* 推測就是因為計算較多的地方都集中在某個區塊,所以執行時間無法再有更好的改善
* 透過 `$ cat /proc/sys/kernel/threads-max` 可以知道每一個 process 的最多 thread 數,我的是 30669
###### tags: `sysprog2016`