owned this note
owned this note
Published
Linked with GitHub
# 2016q3 Homework1 (raytracing)
contributed by <`janetwei`>
reviewed by <`ierosodin`>
- 可以利用gnuplot畫出優化前優化後的差異
- 利用pthread實作raytracing的平行化
- SIMD來加速整個資料的處理
- 程式碼和結果不要用圖片表達
## 開發環境
先載入ㄧ些開發工具
```shell
$ sudo apt-get update
$ sudo apt-get install graphviz
$ sudo apt-get install imagemagick
```
## Raytracing
光線追蹤 (Ray tracing) 是三維電腦圖形學中的描繪演算法 (rendering algorithm),跟蹤從眼睛發出的光線,而非是光源發出的光線,因此與物理現象有些差距
![](https://i.imgur.com/1s1LJv8.png)
convert ppm to png
![](https://i.imgur.com/d7nLfmt.png)
check圖片輸出是否符合預期
以 `make PROFILE=1` 重新編譯程式碼,並且學習 gprof
![](https://i.imgur.com/KuxWWyH.png)
可以發現某些function被呼叫了千萬次
可以試著從這方面去優化程式
## graphviz
因為graphviz需要使用特定格式,所以需要安裝gprof2dot轉換格式
但因為需要用到pip,所以先安裝pyphon
```
$sudo apt install python python-pip
$sudo pip install --upgrade pip
$sudo pip install gprof2dot
```
將gprof所得出的結果用graphviz表示
![](https://i.imgur.com/LXGmGTo.png)
這是用編譯器最佳化選項得出的結果
![](https://i.imgur.com/kuABrjP.png)
時間直接變成0.759s
因為將-O0- 變成 -Ofast-
![](https://i.imgur.com/1q20hd6.png)
但是我們的目標則是手動優化程式的結果比電腦更好(目標好遠大阿QQ)
## dot_product優化(loop unrolling)
由gprof分析 dot_product被呼叫了將近七千萬次,因此第一個想從這裡下手
```
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;
}
```
![](https://i.imgur.com/PHq62DB.png)
將迴圈展開
```
double dot_product(const double *v1, const double *v2)
{
double dp = 0.0;
for (int i = 0; i < 3; i++)
dp = v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2];
return dp;
}
```
在這個做完之後,我才發現這個叫做loop unrolling
![](https://i.imgur.com/EZZDORM.png)
發現已經減少到6.62s了,所以將其他function也用loop unrolling
![](https://i.imgur.com/jH7h0t2.png)
>> 避免用圖片來表達程式碼和輸出的文字訊息,日後才能方便搜尋和複製,敬請配合 [name=jserv]
結果已經減少到5.51s了
![](https://i.imgur.com/vaA1pl7.png)
## static inline
static只對定義(有寫出程式碼)有用
inline 對編譯器有提示的作用,將function called轉成以下的程式碼,利用inline將程式碼展開,少了程式呼叫的成本
但是我們在程式一開始時設定為-O0,最佳化被關掉
所以inline沒有作用,因此我們要強制inline作用(force inline)
將`$ static inline`變成`$ static__attribute__((always_inline))`
![](https://i.imgur.com/UPuPcqR.png)
已經將時間減少為2.638s
```
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
30.18 0.54 0.54 13861875 0.00 0.00 raySphereIntersection
29.62 1.07 0.53 13861875 0.00 0.00 rayRectangularIntersection
11.74 1.28 0.21 2110576 0.00 0.00 compute_specular_diffuse
7.26 1.41 0.13 1048576 0.00 0.00 ray_color
6.15 1.52 0.11 2110576 0.00 0.00 localColor
4.47 1.60 0.08 1048576 0.00 0.00 rayConstruction
4.19 1.68 0.08 4620625 0.00 0.00 ray_hit_object
2.24 1.72 0.04 1241598 0.00 0.00 reflection
1.68 1.75 0.03 1 0.03 1.79 raytracing
1.12 1.77 0.02 1241598 0.00 0.00 protect_color_overflow
0.56 1.78 0.01 1241598 0.00 0.00 refraction
0.56 1.79 0.01 1204003 0.00 0.00 idx_stack_push
0.28 1.79 0.01 113297 0.00 0.00 fresnel
0.00 1.79 0.00 2558386 0.00 0.00 idx_stack_empty
0.00 1.79 0.00 2520791 0.00 0.00 idx_stack_top
0.00 1.79 0.00 1048576 0.00 0.00 idx_stack_init
0.00 1.79 0.00 37595 0.00 0.00 idx_stack_pop
0.00 1.79 0.00 3 0.00 0.00 append_rectangular
0.00 1.79 0.00 3 0.00 0.00 append_sphere
0.00 1.79 0.00 2 0.00 0.00 append_light
0.00 1.79 0.00 1 0.00 0.00 calculateBasisVectors
0.00 1.79 0.00 1 0.00 0.00 delete_light_list
0.00 1.79 0.00 1 0.00 0.00 delete_rectangular_list
```
可以知道接下來從raySphereIntersection著手優化
## OpenMD
此網站上有著簡單介紹平行化處理的方法
https://kheresy.wordpress.com/2006/06/09/
對於一般單一執行緒(single thread)的程式,多核心的處理器並沒有辦法提升它的處理效能;不過對於多執行緒(multi thread)的程式,就可以透過不同的核心同時計算,來達到加速的目的了!
如果要在for迴圈平行化處理,在前面加上一行`$ #pragma omp parallel for`即可
如果想利用 OpenMP 把 mian() 裡面的迴圈平行化處理呢?只需要修改成下面的樣子:
```C
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
void Test( int n )
{
for( int i = 0; i < 10000; ++ i )
{
//do nothing, just waste time
}
printf( "%d, ", n );
}
int main(int argc, char* argv[])
{
#pragma omp parallel for
for( int i = 0; i < 10; ++ i )
Test( i );
system( "pause" );
}
```
只要兩個或以上個for迴圈裏面的變數不是互相獨立,而且不是在平行化處理裡面宣告的變數
要設定成private:在每個thread裡面都有個變數的副本
在function raytracing中在3個for迴圈前加上對3個變數private
以及發現下面也可以做Loop unrolling
```C
void raytracing(uint8_t *pixels, color background_color,
rectangular_node rectangulars, sphere_node spheres,
light_node lights, const viewpoint *view,
int width, int height)
{
point3 u, v, w, d;
color object_color = { 0.0, 0.0, 0.0 };
/* calculate u, v, w */
calculateBasisVectors(u, v, w, view);
idx_stack stk;
int factor = sqrt(SAMPLES);
#pragma omp parallel for num_threads(512)\
private(stk), private(d) \
private(object_color)
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
double r = 0, g = 0, b = 0;
/* MSAA */
// for (int s = 0; s < SAMPLES; s++) {
idx_stack_init(&stk);
rayConstruction(d, u, v, w,
i * factor + 0 / factor,
j * factor + 0 % factor,view,
width * factor, height * factor);
if (ray_color(view->vrp, 0.0, d, &stk, rectangulars, spheres,
lights, object_color,
MAX_REFLECTION_BOUNCES)) {
r += object_color[0];
g += object_color[1];
b += object_color[2];
} else {
r += background_color[0];
g += background_color[1];
b += background_color[2];
}
pixels[((i + (j * width)) * 3) + 0] = r * 255 / SAMPLES;
pixels[((i + (j * width)) * 3) + 1] = g * 255 / SAMPLES;
pixels[((i + (j * width)) * 3) + 2] = b * 255 / SAMPLES;
idx_stack_init(&stk);
rayConstruction(d, u, v, w,
i * factor + 1 / factor,
j * factor + 1 % factor,
view,
width * factor, height * factor);
if (ray_color(view->vrp, 0.0, d, &stk, rectangulars, spheres,
lights, object_color,
MAX_REFLECTION_BOUNCES)) {
r += object_color[0];
g += object_color[1];
b += object_color[2];
} else {
r += background_color[0];
g += background_color[1];
b += background_color[2];
}
pixels[((i + (j * width)) * 3) + 0] = r * 255 / SAMPLES;
pixels[((i + (j * width)) * 3) + 1] = g * 255 / SAMPLES;
pixels[((i + (j * width)) * 3) + 2] = b * 255 / SAMPLES;
idx_stack_init(&stk);
rayConstruction(d, u, v, w,
i * factor + 2 / factor,
j * factor + 2 % factor,
view,
width * factor, height * factor);
if (ray_color(view->vrp, 0.0, d, &stk, rectangulars, spheres,
lights, object_color,
MAX_REFLECTION_BOUNCES)) {
r += object_color[0];
g += object_color[1];
b += object_color[2];
} else {
r += background_color[0];
g += background_color[1];
b += background_color[2];
}
pixels[((i + (j * width)) * 3) + 0] = r * 255 / SAMPLES;
pixels[((i + (j * width)) * 3) + 1] = g * 255 / SAMPLES;
pixels[((i + (j * width)) * 3) + 2] = b * 255 / SAMPLES;
idx_stack_init(&stk);
rayConstruction(d, u, v, w,
i * factor + 3 / factor,
j * factor + 3 % factor,
view,
width * factor, height * factor);
if (ray_color(view->vrp, 0.0, d, &stk, rectangulars, spheres,
lights, object_color,
MAX_REFLECTION_BOUNCES)) {
r += object_color[0];
g += object_color[1];
b += object_color[2];
} else {
r += background_color[0];
g += background_color[1];
b += background_color[2];
}
pixels[((i + (j * width)) * 3) + 0] = r * 255 / SAMPLES;
pixels[((i + (j * width)) * 3) + 1] = g * 255 / SAMPLES;
pixels[((i + (j * width)) * 3) + 2] = b * 255 / SAMPLES;
//}
}
}
}
```
結果變成1.327s了
```
janet@janet-MacBookAir:~/raytracing$ ./raytracing
# Rendering scene
Done!
Execution time of raytracing() : 1.327288 sec
```
因為執行`make PROFILE=1`會比直接`make`多花時間
因此就試試看直接`make`
```
janet@janet-MacBookAir:~/raytracing$ ./raytracing
# Rendering scene
Done!
Execution time of raytracing() : 0.788209 sec
```
之後得到的時間為0.788s
## SIMD
可以同一時間點處理好幾筆資料