Try   HackMD

2016q3 Homework2 (raytracing)

contributed by <mingnus>

tags: raytracing gprof perf profiling sysprog

環境設定

  • Intel Celeron N3150 CPU, 8GB DDR3 RAM
  • Ubuntu 16.04.1, kernel 4.4.0-38
  • gcc (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609
  • GLIBC 2.23-0ubuntu3

gprof

另開專頁記錄: gprof 說明與心得

Raytracing program

由gprof flat profile可看出下列向量運算是最花時間的部分:

  • subtract_vector()
  • dot_product()
  • multiply_vector()
  • add_vector()

Experiments

Loop unrolling + pthread平行運算

  1. 向量運算函式以loop unrolling改寫
  2. 以多執行緒同時繪製畫面不同pixels (問題: raytracing每個pixel真的都是獨立運算? pixel運算順序是否影響結果?)

採用scanline rendering: 考量畫面各區域複雜度不同[1], 為平均運算複雜度, 每個thread各自負責scanlines #(thread_id + k * nr_threads)

如果採用tiled rendering且tile面積又很大時, 程式速度將受制於負責最複雜區域的thread (e.g., cinebench)

barrier實作參考concurrent-ll
https://github.com/gavra0/ConcLinkedList

FIXME: handle trailing rows

Result

  • Loop unrolling大約可節省1/3運算時間
  • 4-threads比gcc -O3快, 輸出的ppm checksum也相同
    但是用gprof看, 多執行緒的vector function呼叫次數卻比單執行緒少?

研究中項目

SIMD

https://gcc.gnu.org/onlinedocs/gcc-5.4.0/gcc/x86-Options.html

gcc預設就有開啟SSE (根據build machine CPU決定, 待查), 以dot_product()為例:

  • simd_t.c:
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; 
}
  • Disassemble dot_product() (注意大小寫):
$ gcc -c simd_t.c -g -O0
$ objdump [-Mintel] -d simd_t.o > simd_t.s

00000000000000b6 <dot_product>:
  b6:	55                   	push   %rbp
  b7:	48 89 e5             	mov    %rsp,%rbp
  ba:	48 89 7d e8          	mov    %rdi,-0x18(%rbp)
  be:	48 89 75 e0          	mov    %rsi,-0x20(%rbp)
  c2:	66 0f ef c0          	pxor   %xmm0,%xmm0
  c6:	f2 0f 11 45 f8       	movsd  %xmm0,-0x8(%rbp)
  cb:	c7 45 f4 00 00 00 00 	movl   $0x0,-0xc(%rbp)
  d2:	eb 46                	jmp    11a <dot_product+0x64>
  d4:	8b 45 f4             	mov    -0xc(%rbp),%eax
  d7:	48 98                	cltq   
  d9:	48 8d 14 c5 00 00 00 	lea    0x0(,%rax,8),%rdx
  e0:	00 
  e1:	48 8b 45 e8          	mov    -0x18(%rbp),%rax
  e5:	48 01 d0             	add    %rdx,%rax
  e8:	f2 0f 10 08          	movsd  (%rax),%xmm1
  ec:	8b 45 f4             	mov    -0xc(%rbp),%eax
  ef:	48 98                	cltq   
  f1:	48 8d 14 c5 00 00 00 	lea    0x0(,%rax,8),%rdx
  f8:	00 
  f9:	48 8b 45 e0          	mov    -0x20(%rbp),%rax
  fd:	48 01 d0             	add    %rdx,%rax
 100:	f2 0f 10 00          	movsd  (%rax),%xmm0
 104:	f2 0f 59 c1          	mulsd  %xmm1,%xmm0
 108:	f2 0f 10 4d f8       	movsd  -0x8(%rbp),%xmm1
 10d:	f2 0f 58 c1          	addsd  %xmm1,%xmm0
 111:	f2 0f 11 45 f8       	movsd  %xmm0,-0x8(%rbp)
 116:	83 45 f4 01          	addl   $0x1,-0xc(%rbp)
 11a:	83 7d f4 02          	cmpl   $0x2,-0xc(%rbp)
 11e:	7e b4                	jle    d4 <dot_product+0x1e>
 120:	f2 0f 10 45 f8       	movsd  -0x8(%rbp),%xmm0
 125:	5d                   	pop    %rbp
 126:	c3                   	retq   

所以我們應該用資料寬度更大的ymm暫存器及AVX指令,才有機會勝過上述ASM code
如果用gcc -mavx option, 則gcc只是把SSE指令換成AVX指令, 並沒有使用ymm暫存器, 實測速度也沒有變快

  • pxor => vxorpd
  • movsd => vmovsd
  • mulsd => vmulsd
  • addsd => vaddsd

使用-mno-sse無法通過編譯 (待查)

Fast inverse square root

https://en.wikipedia.org/wiki/Fast_inverse_square_root
http://213style.blogspot.tw/2014/07/john-carmack.html
Hacker's Delight, 2/e, Ch.11-1 and 17-4

其他

  • 程式分析
    • 世界座標: 右手座標, Z軸在上
    • 場景物件(models.inc): 白球1, 金屬球2, 綠球3; 白矩型1, 紅矩型2, 藍矩型3