contributed by <ChenYi
>
nekoneko
Destribution: Ubuntu 14.04.5 LTS
CPU : Intel® Xeon® CPU E3-1231 v3 @ 3.40GHz
避免用圖片表示程式輸出的文字訊息,請愛用 command line jserv
好的 謝謝提醒 等等修正 ChenYi
弄好環境,clone下來
sudo apt-get update
sudo apt-get install graphviz
sudo apt-get install imagemagick
sudo apt-get install gprof
git clone https://github.com/sysprog21/raytracing
make PROFILE=1
ifeq ($(strip $(PROFILE)),1)
PROF_FLAGS = -pg <======== this
CFLAGS += $(PROF_FLAGS)
LDFLAGS += $(PROF_FLAGS)
endif
執行程式 & gprof
./raytracing
gprof ./raytracing | less
Raytracing結果圖
用上個作業學到的perf來看看cache misses如何
Execution time of raytracing() : 3.315326 sec
Performance counter stats for './raytracing':
22,843 cache-misses # 18.615 % of all cache refs
122,711 cache-references
3,238,239 L1-dcache-load-misses
<not supported> L1-dcache-store-misses
<not supported> L1-dcache-prefetch-misses
151,876 L1-icache-load-misses
3.317456277 seconds time elapsed
感覺問題不是出在cahce-misses , 用gprof看一下時間花在哪
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
25.49 0.67 0.67 69646433 0.00 0.00 dot_product
19.02 1.17 0.50 56956357 0.00 0.00 subtract_vector
10.27 1.44 0.27 13861875 0.00 0.00 rayRectangularIntersection
9.51 1.69 0.25 10598450 0.00 0.00 normalize
6.85 1.87 0.18 31410180 0.00 0.00 multiply_vector
從這邊可以看到佔用次數前幾名分別為dot_product(),subtract_vector(),rayRectangularIntersection()
來做點最基礎的最佳化吧!
已知向量相加減,以vec3為例是
v1 + v2 ===> (v1.x+v2.x) , (v1.y+v2.y) , (v1.z+v2.z)
那麼程式碼中的for其實是不需要用到的(loop unrolling)
for (int i = 0; i < 3; i++)
out[i] = a[i] + b[i];
//change to
out[0] = a[0] + b[0];
out[1] = a[1] + b[1];
out[2] = a[2] + b[2];
嘗試去節省sub/add跑for還要比對的時間結果
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
29.38 0.64 0.64 69646433 0.00 0.00 dot_product
12.85 0.92 0.28 13861875 0.00 0.00 rayRectangularIntersection
8.03 1.10 0.18 10598450 0.00 0.00 normalize
7.80 1.27 0.17 13861875 0.00 0.00 raySphereIntersection
7.34 1.43 0.16 31410180 0.00 0.00 multiply_vector
7.34 1.59 0.16 4620625 0.00 0.00 ray_hit_object
6.43 1.73 0.14 17821809 0.00 0.00 cross_product
4.59 1.83 0.10 56956357 0.00 0.00 subtract_vector
4.13 1.92 0.09 1048576 0.00 0.00 ray_color
sub跑到後面了,代表著佔用時間大幅下降 ==> 可行阿!!
改掉dot_product() , multi_vectors(…) , multi_vector(…)
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
17.20 0.33 0.33 69646433 0.00 0.00 dot_product
13.55 0.59 0.26 10598450 0.00 0.00 normalize
11.73 0.82 0.23 13861875 0.00 0.00 raySphereIntersection
11.20 1.03 0.22 56956357 0.00 0.00 subtract_vector
10.94 1.24 0.21 13861875 0.00 0.00 rayRectangularIntersection
分散變平均了,cumulative time也下降了不少
Total execution time
用Gnuplot畫一下
Origin: Execution time of raytracing() : 4.368682 sec
Remove branch: Execution time of raytracing() : 3.399409 sec
用「表格」來比較,醒目 jserv
感謝老師提醒 順便請問一下表格該怎麼做比較好chenyi
嘗試一下使用OpenMP來做平行化
修改substract_vector(…)
#pragma omp parallel sections
{
#pragma omp section
{
out[0] = a[0] - b[0];
}
#pragma omp section
{
out[1] = a[1] - b[1];
}
#pragma omp section
{
out[2] = a[2] - b[2];
}
}
結果
Rendering scene
Done!
Execution time of raytracing() : 67.389720 sec
恩… 我思考了很久也看過春季班同學的hackpad[4]了,實在想不出什麼原因
不過就結果而言,我的結果確實是失敗了
後來在其他人的hackpad上看到
對raytacing(…)裡的三層for做平行化的話,其結果是可行
參考TempleJiJi's hackpad
怎麼做的就不放上來了,因為我這邊只有試跑而已
在我的實機上的測試結果如下 (16 threads)
# Rendering scene
Done!
Execution time of raytracing() : 0.391119 sec
loop unrolling版的為1.589252 sec
這邊的測試都有關掉-pg,我的實機測試如果沒關的話,使用OpenMP反而是會比較慢的
大致上想一下為什麼在三層for裡會成功,而在math_toolkit.h裡用section做會失敗
以圖示大概是如此
圖片來源(https://computing.llnl.gov/tutorials/openMP/)
查一下自己CPU是否支援 (我的規格)
SSE支援到4.2 AVX支援到2.0
看了一下SSE是128bits AVX則是256bits
這次作業code裏面是用double , 所以這邊選擇使用AVX
把dot_product(…) 裡的換掉
static inline __attribute__((always_inline))
double dot_product(const double *v1, const double *v2)
{
double tmp[4];
register __m256d reg1 , reg2 , out_reg;
reg1 = _mm256_set_pd(0.0 ,v1[2] , v1[1] , v1[0]);
reg2 = _mm256_set_pd(0.0 ,v2[2] , v2[1] , v2[0]);
out_reg = _mm256_shuffle_pd(reg1 , reg2 , _MM_SHUFFLE(0,1,2,3));
_mm256_store_pd(tmp , out_reg);
return tmp[0]+tmp[1]+tmp[2];
}
在編譯上要記得加個 -mavx
執行花了2.612381 秒
居然比原版的還要慢!
想了一下,慢的點應該出在__mmstore_ps(),這個用途是在於把資料搬回記憶體上
但原版的方式為不斷地累加,正常安排暫存器的話大概會是全放在同一個暫存器上
所以應該會比不斷store的store還來的快一點,詳細情形有時間去找老師問問看好了
在Intel Intrinsics Guide裡的Operation
MEM[mem_addr+255:mem_addr] := a[255:0]
各個最佳化方式的比較圖
從圖中可以看到最快的是對for loop平行化的 OpenMP+loop unrolling,比起原本的程式碼快了有5倍之多。令人訝異的是沒想到AVX+loop unrolling會比原版還慢這點。事實上我還看了一下春季班同學們的筆記,同樣做平行化,pthread的方式又會比OpenMP快了一些,畢竟OpenMP使用上還有不少限制,有時間的話在回頭來做作看pthread的實驗。
fzzzz
'sysprog2016'