contributed by <TempoJiJi
>
TempoJiJi
raytracing
sysprog21
SarahYuHanCheng
之前已對raytracing做過各方面的研究,但會做一次總復習,且會着重在上次未能達到的目標上。
過去的共筆:組別共筆, 個人共筆
測試未優化的程式效能:
# Rendering scene
Done!
Execution time of raytracing() : 3.338328 sec
利用gprof觀察程式效能及行爲,在Makefile的編譯選項裏加入 -pg ,再進行編譯跟執行,就會產生一個gmon.out檔:
$ gprof raytracing gmon.out
% cumulative self self total
time seconds seconds calls s/call s/call name
24.91 0.60 0.60 69646433 0.00 0.00 dot_product
14.12 0.94 0.34 56956357 0.00 0.00 subtract_vector
10.38 1.19 0.25 13861875 0.00 0.00 rayRectangularIntersection
9.34 1.42 0.23 31410180 0.00 0.00 multiply_vector
8.72 1.63 0.21 10598450 0.00 0.00 normalize
7.06 1.80 0.17 4620625 0.00 0.00 ray_hit_object
4.98 1.92 0.12 17836094 0.00 0.00 add_vector
4.57 2.03 0.11 13861875 0.00 0.00 raySphereIntersection
4.15 2.13 0.10 17821809 0.00 0.00 cross_product
2.49 2.19 0.06 1048576 0.00 0.00 ray_color
2.08 2.24 0.05 1048576 0.00 0.00 rayConstruction
這裏可以看到dot_product跟subtrace_vector是最花時間的地方,程式執行期間總共被呼叫了接近7前萬次(dot_product)。
優點:
缺點:
static inline
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;
}
這裏可以看到利用迴圈來計算dot_product,而迴圈會有branch的情況發生,導致效能不好,所以就將迴圈拆開(loop unrolling):
double dp = 0.0;
dp += v1[0] * v2[0];
dp += v1[1] * v2[1];
dp += v1[2] * v2[2];
return dp;
# Rendering scene
Done!
Execution time of raytracing() : 2.999242 sec
可以看到只改一個dot_product時間就快了近0.4秒左右。
inline是向編譯器提出“建議”,把inline的函數在函數位置直接展開(減輕系統負擔(overhead)),編譯器會對比兩者執行時間後選擇執行inline與否。
優點:
在fucntion後面加上 __forceinline
static inline __forceinline
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;
}
編譯加上 -D__forceinline="attribute((always_inline))" 進行編譯:
# Rendering scene
Done!
Execution time of raytracing() : 3.059393 sec
時間比原本的快了0.2秒左右,接下來用gprof觀察function call:
% cumulative self self total
time seconds seconds calls s/call s/call name
34.98 1.00 1.00 13861875 0.00 0.00 rayRectangularIntersection
22.38 1.64 0.64 13861875 0.00 0.00 raySphereIntersection
10.84 1.95 0.31 2110576 0.00 0.00 compute_specular_diffuse
9.44 2.22 0.27 2110576 0.00 0.00 localColor
7.17 2.43 0.21 1048576 0.00 0.00 ray_color
5.42 2.58 0.16 4620625 0.00 0.00 ray_hit_object
4.20 2.70 0.12 1048576 0.00 0.00 rayConstruction
可以看到math_tookit.h裏的function已經不見了
優點:
缺點:
將math_tookit.h裏的function改爲Macro來進行計算,這樣就能減少function call(stack frame的push,pop等行爲)所帶來的時間花費
#define DOT(a,b) ((a[0]*b[0])+(a[1]*b[1])+(a[2]*b[2]))
# Rendering scene
Done!
Execution time of raytracing() : 2.741537 sec
這裏可以看到時間比loop unrolling快了近0.2秒左右,因此決定將math_tookit.h裏的花費較大的function都改爲Macro argument
根據gprof的結果,可以知道哪個function的花費最大,因此將它們都改爲Macro argument即可
#define DOT(a,b) ((a[0]*b[0])+(a[1]*b[1])+(a[2]*b[2]))
#define SUB_VEC(a,b,c) (c[0]=a[0]-b[0], c[1]=a[1]-b[1], c[2]=a[2]-b[2])
#define MVEC(a,b,out) (out[0]=a[0]*(b), out[1]=a[1]*(b), out[2]=a[2]*(b))
#define ADD(a,b,c) (c[0]=a[0]+b[0], c[1]=a[1]+b[1], c[2]=a[2]+b[2])
#define CDOT(a,b,out) (out[0]=(a[1]*b[2])-(a[2]*b[1]),out[1]=(a[2]*b[0])-(a[0]*b[2]),out[2]=(a[0]*b[1])-(a[1]*b[0]))
# Rendering scene
Done!
Execution time of raytracing() : 1.634365 sec
可以看到只改了math_tookit.h整個程式的效能就快了接近1倍,可見程式的效能瓶頸真的是在math_tookit.h裏。
首先來實做OpenMP,在for迴圈上加上:
#pragma omp parallel for num_threads(4) \
private(x), private(y)
num_threads是要建立的執行緒數量。private可以確保變數在各個執行緒裏是獨立的,並不會因爲某個執行緒改變了變數,而影響到其它執行緒。
在raytracing的兩層for迴圈上加上:
#pragma omp parallel for num_threads(4) \
private(stk), private(d), \
private(object_color)
for (int j = 0 ; j < 512; j++) {
for (int i = 0; i < 512; i++) {
double r = 0, g = 0, b = 0;
/* MSAA */
這裏將stk、d、object_color設爲private,是因爲這3個的值是會在各個執行緒裏進行更動。
最後要記得 #include<omp.h> , 以及在編譯選項中加上 -fopenp
執行結果:
num_thread(4)
# Rendering scene
Done!
Execution time of raytracing() : 1.738263 sec
不同的num_thread比較圖:
當function的參數超過一個,在建立pthread的時候需要將所有參數打包起來,才能傳送給function:
typedef struct __ARG {
uint8_t *pixels;
color background;
rectangular_node rectangulars;
sphere_node spheres;
light_node lights;
const viewpoint *View;
int start_j;
point3 u, v, w, d;
} arg;
將圖片分成不同的等份交給不同的執行緒去進行,這裏我會先將row分成4個等份,再用4個執行緒去執行:
/* (*data).start_j 爲每個執行緒所執行的起點 */
for (int j = (*data).start_j ; j < 512; j+=MAXX) {
for (int i = 0; i < 512; i++) {
double r = 0, g = 0, b = 0;
/* MSAA */
...
執行結果:
# Rendering scene
Done!
Execution time of raytracing() : 1.603646 sec
不同thread數量的比較圖:
由上圖可得知,thread的數量越多,不代表程式效能越好。
合並所有方法,再跟不同的編譯優化做對比: