# 2016q3 Homework1 (raytracing) contributed by <`TotallyWrong`> ###### tags: `TotallyWrong` `On going` ### ::: Reviewd by <`andy19950`> - 使用很多種方法來嘗試修改 dot_product 很好,但是這份程式碼還有很多修改後可以增加效能的地方,可以詳讀程式碼後嘗試看看其他部份的優化。 - 有把程式碼轉成組合語言來做判斷,如果能夠了解組語的變動的原因後簡單的告訴我們 "為何這樣會造成執行時間增加" 會更好。 - 這份報告比較欠缺圖表分析的方式來顯示不同方式之間的差異,比較不直觀一點。 - 最後就是這個部份的 github 連結是錯的,記得把它修改過來。 --- ## 開發作業環境 **CPU** : Intel Core i5-5200U **Cache size**: L1 Cache 128KB, L2 Cache 512K, L3 Cache 3072KB **Operating System** : Ubuntu 15.10 Wily Werewolf --- **Feature**: * MMX instructions * SSE / Streaming SIMD Extensions * SSE2 / Streaming SIMD Extensions 2 * SSE3 / Streaming SIMD Extensions 3 * SSSE3 / Supplemental Streaming SIMD Extensions 3 * SSE4 / SSE4.1 + SSE4.2 / Streaming SIMD Extensions 4 ? * AES / Advanced Encryption Standard instructions * AVX / Advanced Vector Extensions * AVX2 / Advanced Vector Extensions 2.0 --- ## 使用 Ubuntu 15.10 ### 安狀相關開發工具 ``` $ sudo apt-get update $ sudo apt-get install graphviz $ sudo apt-get install imagemagick ``` --- ### 程式測試 ![](https://i.imgur.com/9rfKh5V.jpg) ---- 跑完第一次Test Run 結果 ``` Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 27.18 0.69 0.69 69646433 0.00 0.00 dot_product 21.27 1.23 0.54 56956357 0.00 0.00 subtract_vector 9.45 1.47 0.24 13861875 0.00 0.00 rayRectangularIntersection 7.48 1.66 0.19 31410180 0.00 0.00 multiply_vector 6.70 1.83 0.17 13861875 0.00 0.00 raySphereIntersection 4.73 1.95 0.12 17836094 0.00 0.00 add_vector 4.73 2.07 0.12 10598450 0.00 0.00 normalize 4.33 2.18 0.11 17821809 0.00 0.00 cross_product 3.94 2.28 0.10 4620625 0.00 0.00 ray_hit_object 1.97 2.33 0.05 1048576 0.00 0.00 ray_color 1.58 2.37 0.04 4221152 0.00 0.00 multiply_vectors 1.18 2.40 0.03 2520791 0.00 0.00 idx_stack_top 1.18 2.43 0.03 2110576 0.00 0.00 localColor 1.18 2.46 0.03 1 0.03 2.53 raytracing 0.79 2.48 0.02 2110576 0.00 0.00 compute_specular_diffuse 0.79 2.50 0.02 1048576 0.00 0.00 rayConstruction 0.39 2.51 0.01 3838091 0.00 0.00 length 0.39 2.52 0.01 1241598 0.00 0.00 refraction 0.39 2.53 0.01 1204003 0.00 0.00 idx_stack_push ``` --- 原始檔案跑完時的gprof_out.txt 結果顯示前幾個就如同老師的影片中顯示 dot_product function 被叫最多次。 ![](https://i.imgur.com/BnMfm7L.png) ![](https://i.imgur.com/nTwrlmL.png) 而利用Perf跑cache-misses就算清空cache,cache- miss的比例還是有時多有時少,但是時間上則大致差不多。 --- 再閱讀[rnic](https://embedded2016.hackpad.com/2016q1-Homework-2A-GalzL151aZc)學長的筆記,我也嘗試了建立利gprof2dot建立CallGraph![](https://i.imgur.com/qOPcUWh.png)來使用對了解來說很好用。 --- 使用gprof2dot前請先裝 python ``` sudo apt-get install python graphviz ``` 使用 Xdot 請先安裝 ``` sudo apt-get install xdot ``` --- ### 嘗試改善方法 ### 方法一: ### 嘗依如老師再影片中所提到的強制compiler進行inline的作法,在Google搜尋後找到的[語法](https://gcc.gnu.org/onlinedocs/gcc/Inline.html) ![](https://i.imgur.com/yWrycQd.png) --- 結果大約有0.1秒的改進,但似乎點難已確認因為測試多次結果都有浮動,在改善很小時難已確認。 >有發現似乎電腦再執行他程式的話對cache test 的時間結果有影響 ### 方法二: ### 接下來嘗試把幾個主要的記算函式的回圈展開,這個作法主要是為了減少branch所造成的Control Harzard。我把最常見的前3個Function dot_product,subtract_vector, multiply_vector都Urolling 。 --- ``` Performance counter stats for './raytracing' (50 runs): 122,126 cache-misses # 22.979 % of all cache refs 552,830 cache-references 11,826,508,383 instructions # 2.25 insns per cycle 5,238,499,149 cycles 1.969861040 seconds time elapsed ( +- 0.19% ) ``` ![](https://i.imgur.com/IX8DpNj.png) 在加總方法一和方法二整體約改善一秒鐘。 --- ### 方法三 : ### 接下來就繼續嘗試使用SIMD,雖然在影片中似乎有人嘗試過而效果並不好,但還是需要了解一下使用方法而且測試一下。[**AVX**](https://software.intel.com/en-us/articles/using-avx-without-writing-avx-code) 在讀過相關資料後了解大致上,Scalar 就是拿一個比較大的記憶體來裝數個較小的值來進行同時運算, 以下兩個網頁是讓我了解SIMD和如何在GCC中使用這種記憶體。 --- 第3個網頁是搜尋到有人對dot_product 的研究,似乎AVX在相當大的Vector的dot_product還是有幫助。 [**什麼是SIMD,**](https://www.kernel.org/pub/linux/kernel/people/geoff/cell/ps3-linux-docs/CellProgrammingTutorial/BasicsOfSIMDProgramming.html) [**怎麼在GCC上Code_Intel_SIMD,**](http://www.codeproject.com/Articles/874396/Crunching-Numbers-with-AVX-and-AVX) [**Dot Product 的改善**](http://blog.theincredibleholk.org/blog/2012/12/10/optimizing-dot-product/) ![](https://i.imgur.com/y6MyamJ.png) --- Dot Product 的程式碼被改成如下 ```clike= inline double dot_product(const double *v1, const double *v2) __attribute__((always_inline)); double dot_product(const double *v1, const double *v2) { double dp=0.0; /* Initialize AVX vector */ __m256d v1vector = _mm256_set_pd(v1[0],v1[1],v1[2],0); __m256d v2vector = _mm256_set_pd(v2[0],v2[1],v2[2],0); /* Compute the product between the two vectors */ __m256d result = _mm256_mul_pd(v1vector,v2vector); /* Sum the elements of the result vector */ double* f = (double*)&result ; dp= f[0]+f[1]+f[2]+f[3] ; return dp; } ``` --- 經過測試時間變長了 ``` #Rendering scene Done! Execution time of raytracing() : 4.320740 sec ``` ![](https://i.imgur.com/ggLmDqm.png) 而檢查Assembly Code 似乎建構SIMD的Assembly Code很長跟原始資表雖然幾乎沒有使用到Jmp的code,不知道這是否可以解釋為使用這種資料結構的成本不小。 ``` //static inline inline double dot_product(const double *v1, const double *v2) __attribute__((always_inline)); double dot_product(const double *v1, const double *v2) { 3f4: 4c 8d 54 24 08 lea r10,[rsp+0x8] 3f9: 48 83 e4 e0 and rsp,0xffffffffffffffe0 3fd: 41 ff 72 f8 push QWORD PTR [r10-0x8] 401: 55 push rbp 402: 48 89 e5 mov rbp,rsp 405: 41 52 push r10 407: 48 81 ec 28 01 00 00 sub rsp,0x128 40e: 48 89 bd d8 fe ff ff mov QWORD PTR [rbp-0x128],rdi 415: 48 89 b5 d0 fe ff ff mov QWORD PTR [rbp-0x130],rsi 41c: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28 423: 00 00 425: 48 89 45 e8 mov QWORD PTR [rbp-0x18],rax 429: 31 c0 xor eax,eax double dp=0.0; 42b: c5 f9 57 c0 vxorpd xmm0,xmm0,xmm0 42f: c5 fb 11 85 e0 fe ff vmovsd QWORD PTR [rbp-0x120],xmm0 436: ff /* Initialize AVX vector */ __m256d v1vector = _mm256_set_pd(v1[0],v1[1],v1[2],0); 437: 48 8b 85 d8 fe ff ff mov rax,QWORD PTR [rbp-0x128] 43e: 48 83 c0 10 add rax,0x10 442: c5 fb 10 00 vmovsd xmm0,QWORD PTR [rax] 446: 48 8b 85 d8 fe ff ff mov rax,QWORD PTR [rbp-0x128] 44d: 48 83 c0 08 add rax,0x8 451: c5 fb 10 08 vmovsd xmm1,QWORD PTR [rax] 455: 48 8b 85 d8 fe ff ff mov rax,QWORD PTR [rbp-0x128] 45c: c5 fb 10 10 vmovsd xmm2,QWORD PTR [rax] 460: c5 fb 11 95 e8 fe ff vmovsd QWORD PTR [rbp-0x118],xmm2 467: ff 468: c5 fb 11 8d 18 ff ff vmovsd QWORD PTR [rbp-0xe8],xmm1 46f: ff 470: c5 fb 11 85 20 ff ff vmovsd QWORD PTR [rbp-0xe0],xmm0 477: ff 478: c5 f9 57 c0 vxorpd xmm0,xmm0,xmm0 47c: c5 fb 11 85 28 ff ff vmovsd QWORD PTR [rbp-0xd8],xmm0 483: ff /* Create the vector [A B C D]. */ extern __inline __m256d __attribute__((__gnu_inline__, __always_inline__, __artificial__)) _mm256_set_pd (double __A, double __B, double __C, double __D) { return __extension__ (__m256d){ __D, __C, __B, __A }; 484: c5 fb 10 85 e8 fe ff vmovsd xmm0,QWORD PTR [rbp-0x118] 48b: ff 48c: c5 fb 10 8d 18 ff ff vmovsd xmm1,QWORD PTR [rbp-0xe8] 493: ff 494: c5 f1 14 c8 vunpcklpd xmm1,xmm1,xmm0 498: c5 fb 10 85 20 ff ff vmovsd xmm0,QWORD PTR [rbp-0xe0] 49f: ff 4a0: c5 fb 10 95 28 ff ff vmovsd xmm2,QWORD PTR [rbp-0xd8] 4a7: ff 4a8: c5 e9 14 c0 vunpcklpd xmm0,xmm2,xmm0 4ac: c4 e3 7d 18 c1 01 vinsertf128 ymm0,ymm0,xmm1,0x1 4b2: c5 fd 29 85 50 ff ff vmovapd YMMWORD PTR [rbp-0xb0],ymm0 4b9: ff __m256d v2vector = _mm256_set_pd(v2[0],v2[1],v2[2],0); 4ba: 48 8b 85 d0 fe ff ff mov rax,QWORD PTR [rbp-0x130] 4c1: 48 83 c0 10 add rax,0x10 4c5: c5 fb 10 00 vmovsd xmm0,QWORD PTR [rax] 4c9: 48 8b 85 d0 fe ff ff mov rax,QWORD PTR [rbp-0x130] 4d0: 48 83 c0 08 add rax,0x8 4d4: c5 fb 10 08 vmovsd xmm1,QWORD PTR [rax] 4d8: 48 8b 85 d0 fe ff ff mov rax,QWORD PTR [rbp-0x130] 4df: c5 fb 10 10 vmovsd xmm2,QWORD PTR [rax] 4e3: c5 fb 11 95 f0 fe ff vmovsd QWORD PTR [rbp-0x110],xmm2 4ea: ff 4eb: c5 fb 11 8d 00 ff ff vmovsd QWORD PTR [rbp-0x100],xmm1 4f2: ff 4f3: c5 fb 11 85 08 ff ff vmovsd QWORD PTR [rbp-0xf8],xmm0 4fa: ff 4fb: c5 f9 57 c0 vxorpd xmm0,xmm0,xmm0 4ff: c5 fb 11 85 10 ff ff vmovsd QWORD PTR [rbp-0xf0],xmm0 506: ff 507: c5 fb 10 85 f0 fe ff vmovsd xmm0,QWORD PTR [rbp-0x110] 50e: ff 50f: c5 fb 10 8d 00 ff ff vmovsd xmm1,QWORD PTR [rbp-0x100] 516: ff 517: c5 f1 14 c8 vunpcklpd xmm1,xmm1,xmm0 51b: c5 fb 10 85 08 ff ff vmovsd xmm0,QWORD PTR [rbp-0xf8] 522: ff 523: c5 fb 10 95 10 ff ff vmovsd xmm2,QWORD PTR [rbp-0xf0] 52a: ff 52b: c5 e9 14 c0 vunpcklpd xmm0,xmm2,xmm0 52f: c4 e3 7d 18 c1 01 vinsertf128 ymm0,ymm0,xmm1,0x1 535: c5 fd 29 85 70 ff ff vmovapd YMMWORD PTR [rbp-0x90],ymm0 53c: ff 53d: c5 fd 28 85 50 ff ff vmovapd ymm0,YMMWORD PTR [rbp-0xb0] 544: ff 545: c5 fd 29 45 90 vmovapd YMMWORD PTR [rbp-0x70],ymm0 54a: c5 fd 28 85 70 ff ff vmovapd ymm0,YMMWORD PTR [rbp-0x90] 551: ff 552: c5 fd 29 45 b0 vmovapd YMMWORD PTR [rbp-0x50],ymm0 } extern __inline __m256d __attribute__((__gnu_inline__, __always_inline__, __artificial__)) _mm256_mul_pd (__m256d __A, __m256d __B) { return (__m256d) ((__v4df)__A * (__v4df)__B); 557: c5 fd 28 45 90 vmovapd ymm0,YMMWORD PTR [rbp-0x70] 55c: c5 fd 59 45 b0 vmulpd ymm0,ymm0,YMMWORD PTR [rbp-0x50] /* Compute the product between the two vectors */ __m256d result = _mm256_mul_pd(v1vector,v2vector); 561: c5 fd 29 85 30 ff ff vmovapd YMMWORD PTR [rbp-0xd0],ymm0 568: ff /* Sum the elements of the result vector */ double* f = (double*)&result ; 569: 48 8d 85 30 ff ff ff lea rax,[rbp-0xd0] 570: 48 89 85 f8 fe ff ff mov QWORD PTR [rbp-0x108],rax dp= f[0]+f[1]+f[2]+f[3] ; 577: 48 8b 85 f8 fe ff ff mov rax,QWORD PTR [rbp-0x108] 57e: c5 fb 10 08 vmovsd xmm1,QWORD PTR [rax] 582: 48 8b 85 f8 fe ff ff mov rax,QWORD PTR [rbp-0x108] 589: 48 83 c0 08 add rax,0x8 58d: c5 fb 10 00 vmovsd xmm0,QWORD PTR [rax] 591: c5 f3 58 c0 vaddsd xmm0,xmm1,xmm0 595: 48 8b 85 f8 fe ff ff mov rax,QWORD PTR [rbp-0x108] 59c: 48 83 c0 10 add rax,0x10 5a0: c5 fb 10 08 vmovsd xmm1,QWORD PTR [rax] 5a4: c5 fb 58 c1 vaddsd xmm0,xmm0,xmm1 5a8: 48 8b 85 f8 fe ff ff mov rax,QWORD PTR [rbp-0x108] 5af: 48 83 c0 18 add rax,0x18 5b3: c5 fb 10 08 vmovsd xmm1,QWORD PTR [rax] 5b7: c5 fb 58 c1 vaddsd xmm0,xmm0,xmm1 5bb: c5 fb 11 85 e0 fe ff vmovsd QWORD PTR [rbp-0x120],xmm0 5c2: ff return dp; 5c3: c5 fb 10 85 e0 fe ff vmovsd xmm0,QWORD PTR [rbp-0x120] 5ca: ff } 5cb: 48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18] 5cf: 64 48 33 04 25 28 00 xor rax,QWORD PTR fs:0x28 5d6: 00 00 5d8: 74 05 je 5df <dot_product+0x1eb> 5da: e8 00 00 00 00 call 5df <dot_product+0x1eb> 5df: 48 81 c4 28 01 00 00 add rsp,0x128 5e6: 41 5a pop r10 5e8: 5d pop rbp 5e9: 49 8d 62 f8 lea rsp,[r10-0x8] 5ed: c3 ret ``` --- ### 方法四 : ### Software Pipelinling [**SoPiP**](http://www.cs.cmu.edu/afs/cs/academic/class/15745-s12/public/lectures/L19-Software-Pipelining-1up.pdf) [**DotPSoPip**](http://www.fic.udc.es/files/asignaturas/81STR/anexos/dsp6m09.pdf) ```clike= 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 Prodcut碼。 --- ```clike= static inline double dot_product(const double *v1, const double *v2) { double dp = 0.0; double pro ; for (int i = 0; i < 3; i++) { pro = v1[i] * v2[i]; dp = dp + pro; } return dp; } ``` 嘗試Software Pipeline。 --- Software Pipeline Dot Product 的效果似乎不是很好甚至比原始作法差,也許是我沒有做成功。 ![](https://i.imgur.com/JFL1yMd.png) --- 試著Output assembly code 看看Software Pipeline 是否有成功 [**GCC Assembly Code**](http://stackoverflow.com/questions/1289881/using-gcc-to-produce-readable-assembly) ``` objdump -d -M intel -S raytracing.o > AssembOr.txt ``` --- 發現因為還要加作一個Pro的空間和多做加的動作,也許是因Software Pipeline的目的是增加Throughput而因為這個Forloop只跑三圈所以能夠增加的Throughput有限所以顯現不出效果。 ``` for (int i = 0; i < 3; i++) 409: c7 45 f4 00 00 00 00 mov DWORD PTR [rbp-0xc],0x0 410: eb 46 jmp 458 <dot_product+0x64> dp+=v1[i]*v2[i]; 412: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] 415: 48 98 cdqe 417: 48 8d 14 c5 00 00 00 lea rdx,[rax*8+0x0] 41e: 00 41f: 48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18] 423: 48 01 d0 add rax,rdx 426: f2 0f 10 08 movsd xmm1,QWORD PTR [rax] 42a: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc] 42d: 48 98 cdqe 42f: 48 8d 14 c5 00 00 00 lea rdx,[rax*8+0x0] 436: 00 437: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20] 43b: 48 01 d0 add rax,rdx 43e: f2 0f 10 00 movsd xmm0,QWORD PTR [rax] 442: f2 0f 59 c1 mulsd xmm0,xmm1 446: f2 0f 10 4d f8 movsd xmm1,QWORD PTR [rbp-0x8] 44b: f2 0f 58 c1 addsd xmm0,xmm1 44f: f2 0f 11 45 f8 movsd QWORD PTR [rbp-0x8],xmm0 ``` 原始Code的Assembly ``` double pro ; for (int i = 0; i < 3; i++) 409: c7 45 ec 00 00 00 00 mov DWORD PTR [rbp-0x14],0x0 410: eb 4c jmp 45e <dot_product+0x6a> { pro = v1[i] * v2[i]; 412: 8b 45 ec mov eax,DWORD PTR [rbp-0x14] 415: 48 98 cdqe 417: 48 8d 14 c5 00 00 00 lea rdx,[rax*8+0x0] 41e: 00 41f: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 423: 48 01 d0 add rax,rdx 426: f2 0f 10 08 movsd xmm1,QWORD PTR [rax] 42a: 8b 45 ec mov eax,DWORD PTR [rbp-0x14] 42d: 48 98 cdqe 42f: 48 8d 14 c5 00 00 00 lea rdx,[rax*8+0x0] 436: 00 437: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30] 43b: 48 01 d0 add rax,rdx 43e: f2 0f 10 00 movsd xmm0,QWORD PTR [rax] 442: f2 0f 59 c1 mulsd xmm0,xmm1 446: f2 0f 11 45 f8 movsd QWORD PTR [rbp-0x8],xmm0 dp += pro; 44b: f2 0f 10 45 f0 movsd xmm0,QWORD PTR [rbp-0x10] 450: f2 0f 58 45 f8 addsd xmm0,QWORD PTR [rbp-0x8] 455: f2 0f 11 45 f0 movsd QWORD PTR [rbp-0x10],xmm0 ``` SoftWare Pipline的Assmbly 在閱讀**SoPiP**時我也看到那位講師是把Software Pipeline跟Loop Unrolling做比較而他說Software Pipeline 比較好是因為 Unrolling 的技巧還是要branch,但是目前我們dotProduct只有三圈所以可以完全Unroll也許因為這樣要做出比Urolling 更有效的Software Pipeline 大概不太可能。 --- ### 方法五 : ### 在作業說明中還有OpenMP還有POSIX Thread 還沒有嘗試,先試試OpenMP 完全不懂OpenMP,所以用我最常解決問題的方法[Youtube](https://www.youtube.com/watch?v=nE-xN4Bf8XI&list=PLLX-Q6B8xqZ8n8bwjGdzBJ25X2utwnoEG) ``` gcc -fopenmp filename.c ``` 要Compile OpenMP 只要Flag fopemp ``` export OMP_NUM_THREADS=4 ``` 設定線程的數量 --- 在看了部份Inetel Youtube 的課程前就現來試試看,在看之前的Callgraph 顯示約 94%的 Function Call都是ray_color所以因此試著在此Funcation做Parallel。