SlideShare a Scribd company logo
1 of 42
SSE 를 이용한 최적화와 실제 사용 예 이권일 EA Seoul Studio (BFO)
발표 대상 C/C++ 프로그래머 H/W 및 최적화에 관심 있는 자 GPGPU 를 준비하는 자
SSE (SIMD Streaming Extension) 1999년 펜티엄3 에 처음 포함된 확장 기능 Float Point 및 비교 로직 등 다양한 연산 SSE 전용 128bit XMM 레지스터 8개 추가 MMX 와 달리 거의 모든 기능이 구현됨
x86/x64 레지스터
SIMD 연산 일반연산 1.0 2.0 3.0 4.0 1.0 5.0 6.0 7.0 8.0 5.0 6.0 8.0 10.0 12.0 6.0
__m128자료형 typedef union __declspec(intrin_type) _CRT_ALIGN(16) __m128 { float               		m128_f32[4]; unsigned __int64		m128_u64[2]; __int8              		m128_i8[16]; __int16             		m128_i16[8]; __int32             		m128_i32[4]; __int64             		m128_i64[2]; unsigned __int8     	m128_u8[16]; unsigned __int16    	m128_u16[8]; unsigned __int32    	m128_u32[4];  } __m128; ,[object Object]
SSE 2 부터 새로이 추가된 __int64 와 double 을 지원하기 위한 __m128i, __m128d 자료형도 있음
명령어에 따라 2,4,8,16 SIMD 연산이 수행될 수 있음. 구조체에는 어떤 데이터가 들어 있는지 알 수 없음,[object Object]
편하게 코딩하기 // 산술 연산자 __forceinline__m128operator+(__m128 l, __m128 r)	{ return_mm_add_ps(l,r); } __forceinline__m128operator-(__m128 l, __m128 r)	{ return_mm_sub_ps(l,r); } __forceinline__m128operator*(__m128 l, __m128 r)	{ return_mm_mul_ps(l,r); } __forceinline__m128operator/(__m128 l, __m128 r)	{ return_mm_div_ps(l,r); } __forceinline__m128operator+(__m128 l, float r)		{ return_mm_add_ps(l,_mm_set1_ps(r)); } __forceinline__m128operator-(__m128 l, float r)		{ return_mm_sub_ps(l, _mm_set1_ps(r)); } __forceinline__m128operator*(__m128 l, float r)		{ return_mm_mul_ps(l, _mm_set1_ps(r)); } __forceinline__m128operator/(__m128 l, float r)		{ return_mm_div_ps(l, _mm_set1_ps(r)); } // 논리 연산자 __forceinline__m128operator&(__m128 l, __m128 r)	{ return_mm_and_ps(l,r); } __forceinline__m128operator|(__m128 l, __m128 r)	{ return_mm_or_ps(l,r); } // 비교 연산자 __forceinline__m128operator<(__m128 l, __m128 r)	{ return_mm_cmplt_ps(l,r); } __forceinline__m128operator>(__m128 l, __m128 r)	{ return_mm_cmpgt_ps(l,r); } __forceinline__m128operator<=(__m128 l, __m128 r)	{ return_mm_cmple_ps(l,r); } __forceinline__m128operator>=(__m128 l, __m128 r)	{ return_mm_cmpge_ps(l,r); } __forceinline__m128operator!=(__m128 l, __m128 r)	{ return_mm_cmpneq_ps(l,r); } __forceinline__m128operator==(__m128 l, __m128 r)	{ return_mm_cmpeq_ps(l,r); }
SIMD 정말 4배 빠른가요? // C 버젼  for(size_ti=0; i<count;++i) { 	b[i] = a[i] + a[i]; } -> 실행 시간 49.267 ms // Compiler Intrinsic 버젼 for(size_ti=0; i<count/4;++i) { 	b4[i] = a4[i] + a4[i]; } -> 실행 시간 47.927 ms
메모리 병목!! a[0] b[0] + a[1] b[1] + a[2] B[2] + a[3] b[3] + a[4] + a[5] + a[0] b[0] a[1] b[1] a[2] b[2] a[3] b[3] a[4] a[5] a[6] a[7] + + + + + + +
연산량을 늘리자! sinf() // sin(a) = a – (a^3)/3! + (a^5)/5! – (a^7)/7! … float req_3f = 1.0f / (3.0*2.0*1.0); float req_5f = 1.0f / (5.0*4.0*3.0*2.0*1.0); float req_7f = 1.0f / (7.0*6.0*5.0*4.0*3.0*2.0*1.0); for(size_ti=0; i<count; ++i) { 	b[i] = a[i]  		- a[i]*a[i]*a[i]*req_3f  		+ a[i]*a[i]*a[i]*a[i]*a[i]*req_5f  		- a[i]*a[i]*a[i]*a[i]*a[i]*a[i]*a[i]*req_7f; } -> 실행 시간 111. ms
C 언어의 연산 병목 a[0] b[0] + a[1] b[1] + a[2] b[2] + a[3] b[3] + a[4] + a[0] a[1] a[2] a[3] a[4] b[0] b[1] b[2] b[3] + + + + +
SSE 버젼의 sinf()  // sin(a) = a – (a^3)/3! + (a^5)/5! – (a^7)/7! … __m128 req_3f4 = _mm_set1_ps(req_3f); __m128 req_5f4 = _mm_set1_ps(req_5f); __m128 req_7f4 = _mm_set1_ps(req_7f); for(size_ti=0; i<count/4; ++i) { 	b4[i] = a4[i]  		- a4[i]*a4[i]*a4[i]*req_3f4  		+ a4[i]*a4[i]*a4[i]*a4[i]*a4[i]*req_5f4  		- a4[i]*a4[i]*a4[i]*a4[i]*a4[i]*a4[i]*a4[i]*req_7f4; } -> 실행 시간 48.939 ms
SSE는 아직도 메모리 병목!!  a[0,1,2,3] + b[0,1,2,3] a[4,5,6,7] + b[4,5,6,7] a[8,9,10,11] + b[8,9,10,11] a[12,13,14,15] + b[12,13,14,15] a[16,17,18,19] a[0,1,2,3] b[0,1,2,3] a[4,5,6,7] b[4,5,6,7] a[8,9,10,11] b[8,9,10,11] a[12,13,14,15] b[12,13,14,15] a[16,17,18,19] + + + +
a+a과 sin() 연산 시간이 같다 ? ,[object Object]
SSE 에서 a4[i] + b4[i] 를 구성하는데 6 명령어로 실행되었고 sin() 은 29 명령어로 실행,[object Object]
그러나 컴파일러는 Vectorization을 잘 못한다.,[object Object]
몇배나 빠르다고요?
_mm_stream_ps() // C 버젼  for(size_ti=0; i<count;++i) { 	b[i] = a[i] + a[i]; } -> 실행 시간 49.267 ms // a+a stream 버젼 for(size_ti=0; i<count/4;++i) { _mm_stream_ps((float*)(b4+i), _mm_add_ps(a4[i], a4[i])); } -> 실행 시간 30.114 ms
CPU _mm_stream_ps() 의 작동 Excution Unit L1 Cache L2 Cache WC Buffer Memory BUS Memory
_mm_stream_ps() 는 빠르다 !! Move Aligned Four Packed Single-FP Non Temporal ,[object Object]
쓰기 순서를 보장하지 않으므로 쓰고 바로 읽으면 안됨,[object Object]
Stream 을 추가한 그래프 !!
같은 시간에 더 많은 일을 합시다!!     float Read + Write 시간 : 2.896 ns __m128 Read + Write 시간 : 11.214 ns __m128 Read + Stream 시간 : 6.977 ns
SSE 프로그래밍 메모리 접근 시간이 길어지고 연산시간이 짧아짐에 따라 더 많은 계산을 할 수 있다. 요즘 CPU는 Out-of-Order 로 인해 대부분 비동기 실행을 한다. 적극 이용하자. 병렬화와 병목 문제는 GPGPU 연산에도 동일하게 적용된다. 미래를 대비하자.!!
SSE 를 적용한 예제들
SSE 를 사용한 CPU Skinning Vertex : 1024 * 1024  Bone : 200 4 weight per vertex + normal + tangent SSE 컴파일 옵션이 켜진 C,  SSE최적화 스키닝 없는 C 루프 복사, SSE 루프 복사, memcpy()
C Skinning Code //  Optimized C Version  D3DXMATRIX m = 	b[in->index[0]] * in->blend[0]  			+ b[in->index[1]] * in->blend[1]  			+ b[in->index[2]] * in->blend[2]  			+ b[in->index[3]] * in->blend[3]; out->position.x = in->position.x*m._11 + in->position.y*m._21 + in->position.z*m._31 + m._41; out->position.y = in->position.x*m._12 + in->position.y*m._22 + in->position.z*m._32 + m._42; out->position.z = in->position.x*m._13 + in->position.y*m._23 + in->position.z*m._33 + m._43; out->normal.x = in->normal.x*m._11 + in->normal.y*m._21 + in->normal.z*m._31; out->normal.y = in->normal.x*m._12 + in->normal.y*m._22 + in->normal.z*m._32; out->normal.z = in->normal.x*m._13 + in->normal.y*m._23 + in->normal.z*m._33; out->tangent.x = in->tangent.x*m._11 + in->tangent.y*m._21 + in->tangent.z*m._31; out->tangent.y = in->tangent.x*m._12 + in->tangent.y*m._22 + in->tangent.z*m._32; out->tangent.z = in->tangent.x*m._13 + in->tangent.y*m._23 + in->tangent.z*m._33;
SSE Skinning Code // SSE Code __m128 b0 = _mm_set_ps1(in->blend[0]); __m128 b1 = _mm_set_ps1(in->blend[1]); __m128 b2 = _mm_set_ps1(in->blend[2]); __m128 b3 = _mm_set_ps1(in->blend[3]); __m128* m[4] = { 	(__m128*)( matrix+in->index[0] ),  			(__m128*)( matrix+in->index[1] ),  			(__m128*)( matrix+in->index[2] ),  			(__m128*)( matrix+in->index[3] ) 	}; __m128 m0 = m[0][0]*b0 + m[1][0]*b1 + m[2][0]*b2 + m[3][0]*b3; __m128 m1 = m[0][1]*b0 + m[1][1]*b1 + m[2][1]*b2 + m[3][1]*b3; __m128 m2 = m[0][2]*b0 + m[1][2]*b1 + m[2][2]*b2 + m[3][2]*b3; __m128 m3 = m[0][3]*b0 + m[1][3]*b1 + m[2][3]*b2 + m[3][3]*b3; _mm_stream_ps( out->position, m0*in->position.x+m1*in->position.y+m2*in->position.z+m3 ); _mm_stream_ps( out->normal, m0*in->normal.x+m1*in->normal.y+m2*in->normal.z ); _mm_stream_ps( out->tangent, m0*in->tangent.x+m1*in->tangent.y+m2*in->tangent.z );
SSE Skinning 결과 memcpy() 시간의 80% 로 스키닝을 할 수 있다. 파티클, UI 등에 유용하게 사용할 수있다. Dynamic VB 를 쓰는 동안 계산을 추가로 할 수 있다.
SSE를 사용한 KdTree ,[object Object]
Deep-Narrow Tree 를 만들어야 효율이 좋아지므로 노드가 무척 많아진다.
Tree Node 방문이 전체 처리 시간의 90% 을 차지한다.,[object Object]
kDTree Packet Traverse
KdTree테스트 결과
Scaleform과 SSE Flash 파일을 3D 가속을 받으며 실행 가능하도록 만들어진 라이브러리 Direct3D/OpenGL 및 다양한 렌더링 라이브러리 지원 현재 프로젝트의 UI 제작에 사용 209개 파일 65147 Line 의 Acton Script 와 DXT5 79MB UI 이미지
Scaleform 3.1 의 문제점 복잡한 swf들을 다수 사용할 경우 CPU 사용률이 상당히 높다. 높은 자유도가 GPU에 최적화 되기 어려운 UI 를 만들게 한다. GRendererD3D9 은예제 코드에 가깝고 개발시 H/W 특성이 고려되지 않았다.
Scaleform개선 방향 Client GFx Client GFx GFxQueue Direct3D Direct3D GFxMoveView::Advance() GFxMoveView::Advance() SceneMgr::DrawScene() GFxMoveView::DisplayMT() SceneMgr::DrawScene() GFxQueue::DrawPrim() GFxMoveView::Display() GFxQueue::Flush() ID3DDevice::DrawPrim() 5~15ms/frame ID3DDevice::DrawPrim()
GFxQueue의 Batch 합치기 기능 Batch 합치기를 하기 위해 Vertex 를 Queue 에 넣을때 Transform (TnL) 을 미리 처리 Render State, Texture State 를 체크해서 중복된 렌더링 재설정을 방지 Scene 에서 벗어난 Shape 들안그리는 기능 추가 CPU로 대체된 VertexShader는 삭제, Pixel Shader도 Batch 합치기를 위해 수정
Transform 코드 caseVS_XY16iCF32: { 	XY16iCF32_VERTEX* input = (XY16iCF32_VERTEX*)src + start; for(UINT i=0; i<count; ++i){ 		//output->pos.x = g_x + (input->x * vertexShaderConstant[0].x + input->y * vertexShaderConstant[1].x + vertexShaderConstant[2].x) * g_width; 		//output->pos.y = g_y - (input->x * vertexShaderConstant[0].y + input->y * vertexShaderConstant[1].y + vertexShaderConstant[2].y) * g_height; 		//output->pos.z = 1; 		//output->pos.w = 1; 		//output->color = FlipColor(input->color); 		//output->factor = FlipColor(input->factor); 		//output->tc0.x = input->x * vertexShaderConstant[3].x + input->y * vertexShaderConstant[4].x + vertexShaderConstant[5].x; 		//output->tc0.y = input->x * vertexShaderConstant[3].y + input->y * vertexShaderConstant[4].y + vertexShaderConstant[5].y; 		//aabb.AddPoint(output->pos); __m128 pos = g_pos + ( input->x*vertexShaderConstant[0] + input->y*vertexShaderConstant[1] + vertexShaderConstant[2] ) * g_size; _mm_storeu_ps(output->pos, pos); __m128i colors = _mm_loadl_epi64((__m128i*)&input->color); __m128iunpack = _mm_unpacklo_epi8(colors, g_zero); __m128ishuffle = _mm_shufflelo_epi16(unpack, _MM_SHUFFLE(3,0,1,2)); 		shuffle = _mm_shufflehi_epi16(shuffle, _MM_SHUFFLE(3,0,1,2)); __m128ipacked = _mm_packus_epi16(shuffle, g_zero); _mm_storel_epi64((__m128i*)&output->color, packed);  __m128tc = input->x*vertexShaderConstant[3] + input->y*vertexShaderConstant[4] + vertexShaderConstant[5]; _mm_storeu_ps(output->tc0, tc); aabb_min = _mm_min_ps(aabb_min, pos); aabb_max = _mm_max_ps(aabb_max, pos); 		++output; 		++input; } }
GFxQueue Draw Call 횟수

More Related Content

What's hot

자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012
자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012
자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012
Esun Kim
 
게임 애셋 스트리밍 패치
게임 애셋 스트리밍 패치게임 애셋 스트리밍 패치
게임 애셋 스트리밍 패치
Seungjae Lee
 
Hierachical z Map Occlusion Culling
Hierachical z Map Occlusion CullingHierachical z Map Occlusion Culling
Hierachical z Map Occlusion Culling
YEONG-CHEON YOU
 
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
Esun Kim
 
[0903 구경원] recast 네비메쉬
[0903 구경원] recast 네비메쉬[0903 구경원] recast 네비메쉬
[0903 구경원] recast 네비메쉬
KyeongWon Koo
 

What's hot (20)

Visual Studio를 이용한 어셈블리어 학습 part 2
Visual Studio를 이용한 어셈블리어 학습 part 2Visual Studio를 이용한 어셈블리어 학습 part 2
Visual Studio를 이용한 어셈블리어 학습 part 2
 
유니티의 툰셰이딩을 사용한 3D 애니메이션 표현
유니티의 툰셰이딩을 사용한 3D 애니메이션 표현유니티의 툰셰이딩을 사용한 3D 애니메이션 표현
유니티의 툰셰이딩을 사용한 3D 애니메이션 표현
 
자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012
자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012
자동화된 소스 분석, 처리, 검증을 통한 소스의 불필요한 #if - #endif 제거하기 NDC2012
 
[Kgc2012] deferred forward 이창희
[Kgc2012] deferred forward 이창희[Kgc2012] deferred forward 이창희
[Kgc2012] deferred forward 이창희
 
멀티스레드 렌더링 (Multithreaded rendering)
멀티스레드 렌더링 (Multithreaded rendering)멀티스레드 렌더링 (Multithreaded rendering)
멀티스레드 렌더링 (Multithreaded rendering)
 
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
 
나만의 엔진 개발하기
나만의 엔진 개발하기나만의 엔진 개발하기
나만의 엔진 개발하기
 
게임 애셋 스트리밍 패치
게임 애셋 스트리밍 패치게임 애셋 스트리밍 패치
게임 애셋 스트리밍 패치
 
Dx11 performancereloaded
Dx11 performancereloadedDx11 performancereloaded
Dx11 performancereloaded
 
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - PerfornanceGCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
 
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
 
프레임레이트 향상을 위한 공간분할 및 오브젝트 컬링 기법
프레임레이트 향상을 위한 공간분할 및 오브젝트 컬링 기법프레임레이트 향상을 위한 공간분할 및 오브젝트 컬링 기법
프레임레이트 향상을 위한 공간분할 및 오브젝트 컬링 기법
 
Hierachical z Map Occlusion Culling
Hierachical z Map Occlusion CullingHierachical z Map Occlusion Culling
Hierachical z Map Occlusion Culling
 
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
덤프 파일을 통한 사후 디버깅 실용 테크닉 NDC2012
 
[0903 구경원] recast 네비메쉬
[0903 구경원] recast 네비메쉬[0903 구경원] recast 네비메쉬
[0903 구경원] recast 네비메쉬
 
Unite2019 HLOD를 활용한 대규모 씬 제작 방법
Unite2019 HLOD를 활용한 대규모 씬 제작 방법Unite2019 HLOD를 활용한 대규모 씬 제작 방법
Unite2019 HLOD를 활용한 대규모 씬 제작 방법
 
Compute shader DX11
Compute shader DX11Compute shader DX11
Compute shader DX11
 
Ndc11 이창희_hdr
Ndc11 이창희_hdrNdc11 이창희_hdr
Ndc11 이창희_hdr
 
Parallel Graphics in Frostbite - Current & Future (Siggraph 2009)
Parallel Graphics in Frostbite - Current & Future (Siggraph 2009)Parallel Graphics in Frostbite - Current & Future (Siggraph 2009)
Parallel Graphics in Frostbite - Current & Future (Siggraph 2009)
 
게임프로젝트에 적용하는 GPGPU
게임프로젝트에 적용하는 GPGPU게임프로젝트에 적용하는 GPGPU
게임프로젝트에 적용하는 GPGPU
 

Viewers also liked

헤테로지니어스 컴퓨팅 : CPU 에서 GPU 로 옮겨가기
헤테로지니어스 컴퓨팅 :  CPU 에서 GPU 로 옮겨가기헤테로지니어스 컴퓨팅 :  CPU 에서 GPU 로 옮겨가기
헤테로지니어스 컴퓨팅 : CPU 에서 GPU 로 옮겨가기
zupet
 
모바일환경에서의 크로스 플랫폼_3D_렌더링엔진_제작과정
모바일환경에서의 크로스 플랫폼_3D_렌더링엔진_제작과정모바일환경에서의 크로스 플랫폼_3D_렌더링엔진_제작과정
모바일환경에서의 크로스 플랫폼_3D_렌더링엔진_제작과정
funmeate
 
병렬 프로그래밍 패러다임
병렬 프로그래밍 패러다임병렬 프로그래밍 패러다임
병렬 프로그래밍 패러다임
codenavy
 
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
Kiheon Park
 
GameMath-Chapter 01 벡터
GameMath-Chapter 01 벡터GameMath-Chapter 01 벡터
GameMath-Chapter 01 벡터
Mark Choi
 
4.5부동소수점
4.5부동소수점4.5부동소수점
4.5부동소수점
JaeHong Park
 
GameMath-Chapter 13 발사체
GameMath-Chapter 13 발사체GameMath-Chapter 13 발사체
GameMath-Chapter 13 발사체
Mark Choi
 
Digital lighting and rendering
Digital lighting and renderingDigital lighting and rendering
Digital lighting and rendering
JaeHong Park
 

Viewers also liked (20)

헤테로지니어스 컴퓨팅 : CPU 에서 GPU 로 옮겨가기
헤테로지니어스 컴퓨팅 :  CPU 에서 GPU 로 옮겨가기헤테로지니어스 컴퓨팅 :  CPU 에서 GPU 로 옮겨가기
헤테로지니어스 컴퓨팅 : CPU 에서 GPU 로 옮겨가기
 
모바일환경에서의 크로스 플랫폼_3D_렌더링엔진_제작과정
모바일환경에서의 크로스 플랫폼_3D_렌더링엔진_제작과정모바일환경에서의 크로스 플랫폼_3D_렌더링엔진_제작과정
모바일환경에서의 크로스 플랫폼_3D_렌더링엔진_제작과정
 
병렬 프로그래밍 패러다임
병렬 프로그래밍 패러다임병렬 프로그래밍 패러다임
병렬 프로그래밍 패러다임
 
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
박기헌 NDC12 초보 클라이언트 프로그래머의 병렬 프로그래밍 도전기
 
Vectorized processing in_a_nutshell_DeView2014
Vectorized processing in_a_nutshell_DeView2014Vectorized processing in_a_nutshell_DeView2014
Vectorized processing in_a_nutshell_DeView2014
 
Dll파일 호출
Dll파일 호출Dll파일 호출
Dll파일 호출
 
Cocos2d x a to z (상)
Cocos2d x a to z (상)Cocos2d x a to z (상)
Cocos2d x a to z (상)
 
Ccx03.cocos builder
Ccx03.cocos builderCcx03.cocos builder
Ccx03.cocos builder
 
GameMath-Chapter 01 벡터
GameMath-Chapter 01 벡터GameMath-Chapter 01 벡터
GameMath-Chapter 01 벡터
 
[NDC14] 파워포인트로 그래픽 리소스 만들기
[NDC14] 파워포인트로 그래픽 리소스 만들기[NDC14] 파워포인트로 그래픽 리소스 만들기
[NDC14] 파워포인트로 그래픽 리소스 만들기
 
C# 뉴비를 위한 맛보기 2
C# 뉴비를 위한 맛보기 2C# 뉴비를 위한 맛보기 2
C# 뉴비를 위한 맛보기 2
 
투영 공식
투영 공식투영 공식
투영 공식
 
Dom 생성과정
Dom 생성과정Dom 생성과정
Dom 생성과정
 
4.5부동소수점
4.5부동소수점4.5부동소수점
4.5부동소수점
 
3D Engine (ICON 2007)
3D Engine (ICON 2007)3D Engine (ICON 2007)
3D Engine (ICON 2007)
 
게임 개발자로서의 가치 창출
게임 개발자로서의 가치 창출게임 개발자로서의 가치 창출
게임 개발자로서의 가치 창출
 
Silverlight vs flash
Silverlight vs flashSilverlight vs flash
Silverlight vs flash
 
GameMath-Chapter 13 발사체
GameMath-Chapter 13 발사체GameMath-Chapter 13 발사체
GameMath-Chapter 13 발사체
 
Chapter 2, 선형 변환과 행렬 1/2
Chapter 2, 선형 변환과 행렬 1/2Chapter 2, 선형 변환과 행렬 1/2
Chapter 2, 선형 변환과 행렬 1/2
 
Digital lighting and rendering
Digital lighting and renderingDigital lighting and rendering
Digital lighting and rendering
 

Similar to 이권일 Sse 를 이용한 최적화와 실제 사용 예

[조진현]Kgc2012 c++amp
[조진현]Kgc2012 c++amp[조진현]Kgc2012 c++amp
[조진현]Kgc2012 c++amp
진현 조
 
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
Esun Kim
 
[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기
진현 조
 
Remote-debugging-based-on-notrace32-20130619-1900
Remote-debugging-based-on-notrace32-20130619-1900Remote-debugging-based-on-notrace32-20130619-1900
Remote-debugging-based-on-notrace32-20130619-1900
Samsung Electronics
 

Similar to 이권일 Sse 를 이용한 최적화와 실제 사용 예 (20)

11_빠른 개발 가능한 레벨 편집 시스템
11_빠른 개발 가능한 레벨 편집 시스템11_빠른 개발 가능한 레벨 편집 시스템
11_빠른 개발 가능한 레벨 편집 시스템
 
[조진현]Kgc2012 c++amp
[조진현]Kgc2012 c++amp[조진현]Kgc2012 c++amp
[조진현]Kgc2012 c++amp
 
ffmpeg optimization using CUDA
ffmpeg optimization using CUDAffmpeg optimization using CUDA
ffmpeg optimization using CUDA
 
불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14 불어오는 변화의 바람, From c++98 to c++11, 14
불어오는 변화의 바람, From c++98 to c++11, 14
 
Deview 2019 눈발자국
Deview 2019 눈발자국Deview 2019 눈발자국
Deview 2019 눈발자국
 
C++17 Key Features Summary - Ver 2
C++17 Key Features Summary - Ver 2C++17 Key Features Summary - Ver 2
C++17 Key Features Summary - Ver 2
 
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
사례를 통해 살펴보는 프로파일링과 최적화 NDC2013
 
[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기[조진현] [Kgc2011]direct x11 이야기
[조진현] [Kgc2011]direct x11 이야기
 
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
2015 제2회 동아리 해커 세미나 - 병렬컴퓨팅 소개 (16기 김정현)
 
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
 
Remote-debugging-based-on-notrace32-20130619-1900
Remote-debugging-based-on-notrace32-20130619-1900Remote-debugging-based-on-notrace32-20130619-1900
Remote-debugging-based-on-notrace32-20130619-1900
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
온라인 게임에서 사례로 살펴보는 디버깅 in NDC2010
 
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
온라인 게임에서 사례로 살펴보는 디버깅 in NDC10
 
[OpenInfra Days Korea 2018] Day 2 - E1: 딥다이브 - OpenStack 생존기
[OpenInfra Days Korea 2018] Day 2 - E1: 딥다이브 - OpenStack 생존기[OpenInfra Days Korea 2018] Day 2 - E1: 딥다이브 - OpenStack 생존기
[OpenInfra Days Korea 2018] Day 2 - E1: 딥다이브 - OpenStack 생존기
 
NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스NDC11_김성익_슈퍼클래스
NDC11_김성익_슈퍼클래스
 
Reproducible research(2)
Reproducible research(2)Reproducible research(2)
Reproducible research(2)
 
하이퍼레저 패브릭 데이터 구조
하이퍼레저 패브릭 데이터 구조하이퍼레저 패브릭 데이터 구조
하이퍼레저 패브릭 데이터 구조
 
R 스터디 세번째
R 스터디 세번째R 스터디 세번째
R 스터디 세번째
 
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이
[2012 CodeEngn Conference 07] nesk - Defcon 20th : 본선 CTF 문제풀이
 
7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성7가지 동시성 모델 - 데이터 병렬성
7가지 동시성 모델 - 데이터 병렬성
 

Recently uploaded

Grid Layout (Kitworks Team Study 장현정 발표자료)
Grid Layout (Kitworks Team Study 장현정 발표자료)Grid Layout (Kitworks Team Study 장현정 발표자료)
Grid Layout (Kitworks Team Study 장현정 발표자료)
Wonjun Hwang
 

Recently uploaded (7)

A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)
 
도심 하늘에서 시속 200km로 비행할 수 있는 미래 항공 모빌리티 'S-A2'
도심 하늘에서 시속 200km로 비행할 수 있는 미래 항공 모빌리티 'S-A2'도심 하늘에서 시속 200km로 비행할 수 있는 미래 항공 모빌리티 'S-A2'
도심 하늘에서 시속 200km로 비행할 수 있는 미래 항공 모빌리티 'S-A2'
 
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
 
캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차
 
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionMOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
 
[Terra] Terra Money: Stability and Adoption
[Terra] Terra Money: Stability and Adoption[Terra] Terra Money: Stability and Adoption
[Terra] Terra Money: Stability and Adoption
 
Grid Layout (Kitworks Team Study 장현정 발표자료)
Grid Layout (Kitworks Team Study 장현정 발표자료)Grid Layout (Kitworks Team Study 장현정 발표자료)
Grid Layout (Kitworks Team Study 장현정 발표자료)
 

이권일 Sse 를 이용한 최적화와 실제 사용 예

  • 1. SSE 를 이용한 최적화와 실제 사용 예 이권일 EA Seoul Studio (BFO)
  • 2. 발표 대상 C/C++ 프로그래머 H/W 및 최적화에 관심 있는 자 GPGPU 를 준비하는 자
  • 3. SSE (SIMD Streaming Extension) 1999년 펜티엄3 에 처음 포함된 확장 기능 Float Point 및 비교 로직 등 다양한 연산 SSE 전용 128bit XMM 레지스터 8개 추가 MMX 와 달리 거의 모든 기능이 구현됨
  • 5. SIMD 연산 일반연산 1.0 2.0 3.0 4.0 1.0 5.0 6.0 7.0 8.0 5.0 6.0 8.0 10.0 12.0 6.0
  • 6.
  • 7. SSE 2 부터 새로이 추가된 __int64 와 double 을 지원하기 위한 __m128i, __m128d 자료형도 있음
  • 8.
  • 9. 편하게 코딩하기 // 산술 연산자 __forceinline__m128operator+(__m128 l, __m128 r) { return_mm_add_ps(l,r); } __forceinline__m128operator-(__m128 l, __m128 r) { return_mm_sub_ps(l,r); } __forceinline__m128operator*(__m128 l, __m128 r) { return_mm_mul_ps(l,r); } __forceinline__m128operator/(__m128 l, __m128 r) { return_mm_div_ps(l,r); } __forceinline__m128operator+(__m128 l, float r) { return_mm_add_ps(l,_mm_set1_ps(r)); } __forceinline__m128operator-(__m128 l, float r) { return_mm_sub_ps(l, _mm_set1_ps(r)); } __forceinline__m128operator*(__m128 l, float r) { return_mm_mul_ps(l, _mm_set1_ps(r)); } __forceinline__m128operator/(__m128 l, float r) { return_mm_div_ps(l, _mm_set1_ps(r)); } // 논리 연산자 __forceinline__m128operator&(__m128 l, __m128 r) { return_mm_and_ps(l,r); } __forceinline__m128operator|(__m128 l, __m128 r) { return_mm_or_ps(l,r); } // 비교 연산자 __forceinline__m128operator<(__m128 l, __m128 r) { return_mm_cmplt_ps(l,r); } __forceinline__m128operator>(__m128 l, __m128 r) { return_mm_cmpgt_ps(l,r); } __forceinline__m128operator<=(__m128 l, __m128 r) { return_mm_cmple_ps(l,r); } __forceinline__m128operator>=(__m128 l, __m128 r) { return_mm_cmpge_ps(l,r); } __forceinline__m128operator!=(__m128 l, __m128 r) { return_mm_cmpneq_ps(l,r); } __forceinline__m128operator==(__m128 l, __m128 r) { return_mm_cmpeq_ps(l,r); }
  • 10. SIMD 정말 4배 빠른가요? // C 버젼 for(size_ti=0; i<count;++i) { b[i] = a[i] + a[i]; } -> 실행 시간 49.267 ms // Compiler Intrinsic 버젼 for(size_ti=0; i<count/4;++i) { b4[i] = a4[i] + a4[i]; } -> 실행 시간 47.927 ms
  • 11. 메모리 병목!! a[0] b[0] + a[1] b[1] + a[2] B[2] + a[3] b[3] + a[4] + a[5] + a[0] b[0] a[1] b[1] a[2] b[2] a[3] b[3] a[4] a[5] a[6] a[7] + + + + + + +
  • 12. 연산량을 늘리자! sinf() // sin(a) = a – (a^3)/3! + (a^5)/5! – (a^7)/7! … float req_3f = 1.0f / (3.0*2.0*1.0); float req_5f = 1.0f / (5.0*4.0*3.0*2.0*1.0); float req_7f = 1.0f / (7.0*6.0*5.0*4.0*3.0*2.0*1.0); for(size_ti=0; i<count; ++i) { b[i] = a[i] - a[i]*a[i]*a[i]*req_3f + a[i]*a[i]*a[i]*a[i]*a[i]*req_5f - a[i]*a[i]*a[i]*a[i]*a[i]*a[i]*a[i]*req_7f; } -> 실행 시간 111. ms
  • 13. C 언어의 연산 병목 a[0] b[0] + a[1] b[1] + a[2] b[2] + a[3] b[3] + a[4] + a[0] a[1] a[2] a[3] a[4] b[0] b[1] b[2] b[3] + + + + +
  • 14. SSE 버젼의 sinf() // sin(a) = a – (a^3)/3! + (a^5)/5! – (a^7)/7! … __m128 req_3f4 = _mm_set1_ps(req_3f); __m128 req_5f4 = _mm_set1_ps(req_5f); __m128 req_7f4 = _mm_set1_ps(req_7f); for(size_ti=0; i<count/4; ++i) { b4[i] = a4[i] - a4[i]*a4[i]*a4[i]*req_3f4 + a4[i]*a4[i]*a4[i]*a4[i]*a4[i]*req_5f4 - a4[i]*a4[i]*a4[i]*a4[i]*a4[i]*a4[i]*a4[i]*req_7f4; } -> 실행 시간 48.939 ms
  • 15. SSE는 아직도 메모리 병목!! a[0,1,2,3] + b[0,1,2,3] a[4,5,6,7] + b[4,5,6,7] a[8,9,10,11] + b[8,9,10,11] a[12,13,14,15] + b[12,13,14,15] a[16,17,18,19] a[0,1,2,3] b[0,1,2,3] a[4,5,6,7] b[4,5,6,7] a[8,9,10,11] b[8,9,10,11] a[12,13,14,15] b[12,13,14,15] a[16,17,18,19] + + + +
  • 16.
  • 17.
  • 18.
  • 20. _mm_stream_ps() // C 버젼 for(size_ti=0; i<count;++i) { b[i] = a[i] + a[i]; } -> 실행 시간 49.267 ms // a+a stream 버젼 for(size_ti=0; i<count/4;++i) { _mm_stream_ps((float*)(b4+i), _mm_add_ps(a4[i], a4[i])); } -> 실행 시간 30.114 ms
  • 21. CPU _mm_stream_ps() 의 작동 Excution Unit L1 Cache L2 Cache WC Buffer Memory BUS Memory
  • 22.
  • 23.
  • 24. Stream 을 추가한 그래프 !!
  • 25. 같은 시간에 더 많은 일을 합시다!! float Read + Write 시간 : 2.896 ns __m128 Read + Write 시간 : 11.214 ns __m128 Read + Stream 시간 : 6.977 ns
  • 26. SSE 프로그래밍 메모리 접근 시간이 길어지고 연산시간이 짧아짐에 따라 더 많은 계산을 할 수 있다. 요즘 CPU는 Out-of-Order 로 인해 대부분 비동기 실행을 한다. 적극 이용하자. 병렬화와 병목 문제는 GPGPU 연산에도 동일하게 적용된다. 미래를 대비하자.!!
  • 27. SSE 를 적용한 예제들
  • 28. SSE 를 사용한 CPU Skinning Vertex : 1024 * 1024 Bone : 200 4 weight per vertex + normal + tangent SSE 컴파일 옵션이 켜진 C, SSE최적화 스키닝 없는 C 루프 복사, SSE 루프 복사, memcpy()
  • 29. C Skinning Code // Optimized C Version D3DXMATRIX m = b[in->index[0]] * in->blend[0] + b[in->index[1]] * in->blend[1] + b[in->index[2]] * in->blend[2] + b[in->index[3]] * in->blend[3]; out->position.x = in->position.x*m._11 + in->position.y*m._21 + in->position.z*m._31 + m._41; out->position.y = in->position.x*m._12 + in->position.y*m._22 + in->position.z*m._32 + m._42; out->position.z = in->position.x*m._13 + in->position.y*m._23 + in->position.z*m._33 + m._43; out->normal.x = in->normal.x*m._11 + in->normal.y*m._21 + in->normal.z*m._31; out->normal.y = in->normal.x*m._12 + in->normal.y*m._22 + in->normal.z*m._32; out->normal.z = in->normal.x*m._13 + in->normal.y*m._23 + in->normal.z*m._33; out->tangent.x = in->tangent.x*m._11 + in->tangent.y*m._21 + in->tangent.z*m._31; out->tangent.y = in->tangent.x*m._12 + in->tangent.y*m._22 + in->tangent.z*m._32; out->tangent.z = in->tangent.x*m._13 + in->tangent.y*m._23 + in->tangent.z*m._33;
  • 30. SSE Skinning Code // SSE Code __m128 b0 = _mm_set_ps1(in->blend[0]); __m128 b1 = _mm_set_ps1(in->blend[1]); __m128 b2 = _mm_set_ps1(in->blend[2]); __m128 b3 = _mm_set_ps1(in->blend[3]); __m128* m[4] = { (__m128*)( matrix+in->index[0] ), (__m128*)( matrix+in->index[1] ), (__m128*)( matrix+in->index[2] ), (__m128*)( matrix+in->index[3] ) }; __m128 m0 = m[0][0]*b0 + m[1][0]*b1 + m[2][0]*b2 + m[3][0]*b3; __m128 m1 = m[0][1]*b0 + m[1][1]*b1 + m[2][1]*b2 + m[3][1]*b3; __m128 m2 = m[0][2]*b0 + m[1][2]*b1 + m[2][2]*b2 + m[3][2]*b3; __m128 m3 = m[0][3]*b0 + m[1][3]*b1 + m[2][3]*b2 + m[3][3]*b3; _mm_stream_ps( out->position, m0*in->position.x+m1*in->position.y+m2*in->position.z+m3 ); _mm_stream_ps( out->normal, m0*in->normal.x+m1*in->normal.y+m2*in->normal.z ); _mm_stream_ps( out->tangent, m0*in->tangent.x+m1*in->tangent.y+m2*in->tangent.z );
  • 31. SSE Skinning 결과 memcpy() 시간의 80% 로 스키닝을 할 수 있다. 파티클, UI 등에 유용하게 사용할 수있다. Dynamic VB 를 쓰는 동안 계산을 추가로 할 수 있다.
  • 32.
  • 33. Deep-Narrow Tree 를 만들어야 효율이 좋아지므로 노드가 무척 많아진다.
  • 34.
  • 37. Scaleform과 SSE Flash 파일을 3D 가속을 받으며 실행 가능하도록 만들어진 라이브러리 Direct3D/OpenGL 및 다양한 렌더링 라이브러리 지원 현재 프로젝트의 UI 제작에 사용 209개 파일 65147 Line 의 Acton Script 와 DXT5 79MB UI 이미지
  • 38. Scaleform 3.1 의 문제점 복잡한 swf들을 다수 사용할 경우 CPU 사용률이 상당히 높다. 높은 자유도가 GPU에 최적화 되기 어려운 UI 를 만들게 한다. GRendererD3D9 은예제 코드에 가깝고 개발시 H/W 특성이 고려되지 않았다.
  • 39. Scaleform개선 방향 Client GFx Client GFx GFxQueue Direct3D Direct3D GFxMoveView::Advance() GFxMoveView::Advance() SceneMgr::DrawScene() GFxMoveView::DisplayMT() SceneMgr::DrawScene() GFxQueue::DrawPrim() GFxMoveView::Display() GFxQueue::Flush() ID3DDevice::DrawPrim() 5~15ms/frame ID3DDevice::DrawPrim()
  • 40. GFxQueue의 Batch 합치기 기능 Batch 합치기를 하기 위해 Vertex 를 Queue 에 넣을때 Transform (TnL) 을 미리 처리 Render State, Texture State 를 체크해서 중복된 렌더링 재설정을 방지 Scene 에서 벗어난 Shape 들안그리는 기능 추가 CPU로 대체된 VertexShader는 삭제, Pixel Shader도 Batch 합치기를 위해 수정
  • 41. Transform 코드 caseVS_XY16iCF32: { XY16iCF32_VERTEX* input = (XY16iCF32_VERTEX*)src + start; for(UINT i=0; i<count; ++i){ //output->pos.x = g_x + (input->x * vertexShaderConstant[0].x + input->y * vertexShaderConstant[1].x + vertexShaderConstant[2].x) * g_width; //output->pos.y = g_y - (input->x * vertexShaderConstant[0].y + input->y * vertexShaderConstant[1].y + vertexShaderConstant[2].y) * g_height; //output->pos.z = 1; //output->pos.w = 1; //output->color = FlipColor(input->color); //output->factor = FlipColor(input->factor); //output->tc0.x = input->x * vertexShaderConstant[3].x + input->y * vertexShaderConstant[4].x + vertexShaderConstant[5].x; //output->tc0.y = input->x * vertexShaderConstant[3].y + input->y * vertexShaderConstant[4].y + vertexShaderConstant[5].y; //aabb.AddPoint(output->pos); __m128 pos = g_pos + ( input->x*vertexShaderConstant[0] + input->y*vertexShaderConstant[1] + vertexShaderConstant[2] ) * g_size; _mm_storeu_ps(output->pos, pos); __m128i colors = _mm_loadl_epi64((__m128i*)&input->color); __m128iunpack = _mm_unpacklo_epi8(colors, g_zero); __m128ishuffle = _mm_shufflelo_epi16(unpack, _MM_SHUFFLE(3,0,1,2)); shuffle = _mm_shufflehi_epi16(shuffle, _MM_SHUFFLE(3,0,1,2)); __m128ipacked = _mm_packus_epi16(shuffle, g_zero); _mm_storel_epi64((__m128i*)&output->color, packed); __m128tc = input->x*vertexShaderConstant[3] + input->y*vertexShaderConstant[4] + vertexShaderConstant[5]; _mm_storeu_ps(output->tc0, tc); aabb_min = _mm_min_ps(aabb_min, pos); aabb_max = _mm_max_ps(aabb_max, pos); ++output; ++input; } }
  • 43. GFx Renderer 코멘트 GRenderD3D9 코드가 구리다. 프로그래머라면 찬찬히 분석한다음 여러군데 손을 봐두자. UI 아티스트는 GPU 최적화에신경쓰지 않는다. 초기 단게부터 적절한 레이아웃과 컴포넌트를 설계해두자. GFxExport에서 DXTn포맷을 무조건 2의 배수로 Resize 해버려 저장하는 경우가 있다. GFxExport에서 Texture Atlas 기능을 쓰는 것도 최적화에 큰 도움이 된다.
  • 44. ?

Editor's Notes

  1. 이 그림은 일반적인 프로그래밍을 할때 32비트, 64비트 프로그래밍을 할때 사용하는 레지스터들입니다.왼쪽에 있는 RAX 부터 R15까지가 범용 레지스터로 RSP 까지의 흰색 부분이 32비트 범용 레지스터이고 그것을 R15 까지 확장한 것이 64비트 범용 레지스터입니다.가운데 있는 것이 64비트 크기의 FPU/MMX 겸용으로 사용되는 레지스터입니다. FPU는 이것을 80비트까지 확장해서 쓰기도 하는데 64비트 프로그래밍 시에는 이 부분을 사용하지 않도록 권장하고 있습니다.그리고 마지막 SSE 용 128 비트 레지스터인 XMM 레지스터 입니다. 0번부터 7번까지는 32비트에서 사용하고 64비트에서는 15번까지 전부 사용할 수 있습니다.크기로 비교해보면 32비트 모드일때나 64비트 모드일때나 XMM 레지스터 크기가 훨씬 크다는 것을 알 수 있습니다.그만큼 더 많은 일을 시킬 수 있다는 뜻이죠.
  2. XMM 레지스터가 이렇게 거대해진 이유는 SIMD 연산을 하기 위해서 입니다.SIMD 는 Single Instruction Multi Data 의 약자라고 했는데 이 그림을 보면 쉽게 이해하실 수 있을겁니다.왼쪽에 있는 것이 일반적인 연산이라면 오른쪽 같이 4개를 한꺼번에 처리하는 것이 SIMD 처리입니다.SIMD는 하나의 명령으로 여러개의 데이터를 동시에 처리하기 때문에 다량의 데이터를 처리해야할때 매우 효율적입니다.
  3. 이렇게 빠른 작동을 할 수 있는 것은 Write Combining 버퍼라는 것 때문입니다.처음 소개된 것은 PCI 버스 시절인데 버스에 접근 할때는 캐쉬를 끄고 접근하기 때문에 접근 속도가 매우 느려지는데그것을 보완하기 위해서 메모리 컨트롤러단에 몇십 바이트 크기의 쓰기 버퍼를 만들어 놓고 캐쉬를 통하지 않는 쓰기 작업을 도와주도록 한 것입니다.이것은 그래픽 카드에 많이 쓰였고 옛날 Direct Draw 프로그래밍 시절 Surface 를 읽어오는 것에 비해 쓰는 것이 월등히 빨랐던 것도 WC 버퍼 덕분입니다._mm_stream_ps() 명령은 이 WC 버퍼를 통해 데이터 쓰기 작업을 도와줍니다.
  4. SSE 프로그래밍을 사용한 예제를 세가지 준비했습니다.모든 예제들은 실제 상용화된 프로젝트나 내부 툴 코드에 사용된 경험이 있는 것들이고 더 자세한 정보가 필요하신 분들은 개별적으로 접촉하시면 상세한 정보를 나눠드릴 수 있습니다.
  5. 마지막으로 Scaleform최적화 부분입니다.사실 Scaleform최적화는 별도 세션으로 다루고 싶었는데 준비 시간 문제도 있었고저는 스케일폼보다 SSE 최적화쪽이 훨씬 익숙하기 때문에 아직 준비가 덜 되었다고생각되어서 이렇게 이 밑에 붙였습니다.스케일폼은 게임내에서 매크로 미디어 플래쉬 파일을 구동할 수 있게 해주는 라이브러리입니다. 요즘 게임 UI 개발에 많이 사용하고 있고 저희 팀도 꽤 일찍 도입해서 아주 많은부분을 GFx에 의지하고 있습니다.참고로 저희 프로젝트에서는 SWF 파일이 209개이고 액션 스크립트만 65000라인, 그리고배경 이미지를 제외한 UI 컴포넌트에 쓰인 이미지만 압축텍스쳐로 79MB에 달합니다.
  6. Scaleform을 최적화 하게된 이유는 저희 프로젝트에서 아주 많은 부분을이 라이브러리에 의존하고 있으며 덕분에 상당히 많은 부하가 GFx에걸리고 있기 때문입니다. 최적화 이전에 부하가 심할 경우 15ms 정도가 꾸준히GFx에 의해서 사용되었고 GFx하나만으로 프레임 레이트가 팍팍 떨어지는문제를 발생시키고 있었습니다.어쨌거나 GFx라이브러리는 방대하고 코드 핵심 부분은 고치기 어려운 만큼소극적인 방법으로 최적화를 진행했고 여러가지 방법을 동원해서 게임에 부하를획기적으로 줄일 수 있도록 부하를 분산할 수 잇었습니다.이자리를 빌어 GFx최적화 아이디어를 주신 전 네오위즈 최의종 팀장님께 감사의말씀을 드립니다.
  7. GFx의 최대 문제점은 Display 를 호출한 시점에 여러가지 CPU 연산을 수행하고 그 다음 3D 렌더링이 진행된다는 점입니다. CPU 부하가 적은 프로젝트에서는 큰부하가 되지 않지만 CPU 부하가 심할 경우 이러한 부하는 매우 부담스럽습니다.저희 프로젝트의 경우 최종적으로 전체 swf파일을 그리는 Display() 를 호출했을때5~15ms 까지의 시간이 걸렸었습니다. 여기서 아주 많은 UI 컴포넌트들을 그리는데개중의 많은 시간은 SWF 파일들을 돌아다니며 애니메이션을 처리하고 좌표를 정하는등의 연산 작업이었습니다.게다가 문제는 이 부하는 게임에서 전투가 격해지고 캐릭터들이 화면에 많이 나올때 더더욱 심해졌다는 점입니다. 15ms 정도라면 상당히 긴 시간이라서 GFx렌더링 만 하더라도 60fps 를 겨우 그릴 수 있는 상태였습니다.저희 팀에서는 이 문제를 GRenderer를 멀티쓰레드화 시키는 것으로 해결하였습니다. GFx라이브리 자체는 매우 크고 복잡하기 때문에 쉽게멀티쓰레드화 시키지 힘들지만 다행히도 Display() 함수를 호출한 다음은GFx라이브러리 외부와의 호출이나 데이터 접근이 존재하지 않고 단지화면 출력을 위한 Direct3D 호출만이 이뤄지고 있었습니다.따라서 Display() 함수 자체를 다른 쓰레드로 옮기고 화면에 렌더링하고자하는 데이터들을 전부 큐에 넣은 다음 게임 화면 렌더링이 끝나면 큐에들어있는 데이터를 한번에 그리도록 하였습니다. 이것은 DirectX 11 에 추가된 큐와 매우 흡사한 버젼입니다.
  8. 이 큐는 단순히 Direct3D함수 호출만을 큐하는 것이 아니라 미리 전처리 할 수 있는 것들을처리해두는 기능을 추가하였습니다. 가장 간단한 것으로 RenderState가 중복되어 설정되는것을 막아주고 동일한 텍스쳐와 쉐이더를 사용하는 오브젝트들을 한꺼번에 그릴 수 있도록배치 처리를 해주었습니다. 또한 UI 제작 아티스트 분들이 UI를 감추기 위해 컴포넌트들을 화면 밖으로 살짝 옮겨놓는 경우가 있는데 이런 것들에 대한 호출도 실행하지 않도록 하였죠.GFx는 버텍스 쉐이더에서 행렬을 써서 UI 의 좌표를 지정했는데 배치 합치기와 클립핑 작업을하기 위해서 버텍스 쉐이더 코드를 전부 CPU 단으로 옮겼습니다. 이 코드들은 SSE 로 재작성되었고 몇몇 픽셀 쉐이더 상수들도 버텍스 데이터를 통해 전달할 수 있도록 바뀌었습니다.
  9. 이 코드가 버텍스 쉐이더를 대체한 CPU 코드의 일부입니다. SSE 코딩을 하기 앞서먼저 C 코드로 잘 작동하는 코드를 작성한 다음 SSE 로 옮겼습니다.GFx최적화를 하며 애먹었던게 구형 그래픽 카드인데 프로젝트 스펙상 쉐이더 모델2.0을 지원해야 했고 라데온 9600 과 그 것을 승계한 x1600 같은 그래픽 카드는 Free Vertex Format 을 제대로 지원하지 않앗기 때문에 포맷에 맞춰쳐 Vertex 구조체를 사용해야하는 문제가 발생했습니다. (덕분에 패치 한번 깨먹었습니다.  )속도를 위해 16바이트 정렬된 Vertex 를 사용하였었는데 쉐이더 모델 2.0을 위해하드웨어에 맞게 Vertex 정보를 사용하였고 덕분에 _mm_storeu_ps() 같은 명령을쓰게 되었죠.
  10. 이 그래프는 큐를 이용함으로써 배치 합치기의 결과를 보여주는 그래프 입니다.왼쪽부터 로그인 화면, 로비 화면, 상점 화면, 대기실, 로딩, 게임 화면, 게임내 특수 UI 화면등을 순서대로 띄웠을 때이고 그래프는 각각 화면에서 Draw Call횟수를 나타내고 있습니다.아주 만족스러운건 아니지만 경우에 따라서 1/3 ~ 1/4 정도 Draw Call 횟수가줄어들었으며 큐를 사용함에 따라 CPU 부하가 전부 다른 쓰레드로 감춰진 관계로 2 Core 이상 있는 머신에서는 프레임 레이트가 대폭 향상 되었습니다.참고로 Call횟수가 기대보다 줄어들지 못한 이유는 UI 설계상 이유도 있는데가량 캐릭터 머리위에 떠있는 이름의 경우 아이콘-이름-아이콘-이름이 차례대로 있는 컴포넌트를 그릴 경우 아이콘들이 같은 텍스쳐에 있더라도 큐는상태가 변경됨에 따라 4번의 Draw Call 별도로 호출해줘야 한다는 점 입니다.특히 폰트 텍스쳐가 다른 아이콘이나 UI 들과 별도로 있는 관계로 그리는 순서에 매우 민감하게 반응합니다. 또한 컴포넌트들을 어떻게 조합하냐에 따라한번에 주루룩 그려주는 경우도 있을 수 있고 전부 별도로 그리는 경우도 있기 때문에 디자인 단계에서 최적화를 고려하지 않으면 코드만으로는개선에 한계가 있습니다.
  11. 스케일 폼에 대해 결론을 내자면..일단 스케일 폼에 따라온 GRenderD3D9 코드는 매우 구립니다. 매니지드 텍스쳐도제대로 지원하지 못하고 메모리 관리자도 너무 많은 메모리를 사용합니다. 시간이있다면 차분히 이곳저곳을 분석해서 손을 대줘야 합니다.그리고 UI 아티스트들이 작업하는 초기부터 여러가지 신경써서 작업할 수 있도록해야 합니다. 나중에 손을 대면 늦고 최적화가 힘든 만큼 텍스쳐를 어떻게 쓰고있는지 그리는 순서를 어떻게 하는게 좋은지 미리 미리 알려주고 작업 결과물을틈틈히 체크해 주어야 합니다.GFXExport라는 툴이 있는데 이 툴도 문제가 좀 많습니다. 저희 프로젝트는 압축텍스쳐를 사용하는데 여기 사용된 텍스쳐 압축 라이브러리가 최적화가 꺼져 있다던지 압축을 사용할 경우 무조건 이미지를 2의 배수로 resize 한다던지 문제가 꽤많습니다.그리고 배치 합치기 작업을 계획한다면 GFxExport옵션을 적절히 줘서 텍스쳐아틀라스를 활성화 해주는게 좋습니다. 이게 안되어 있으면 텍스쳐가 바뀌면서모아 그리는게 매우 힘들어지니까요.참고로 멀티쓰레드를 써서 GFx가 소모하는 시간은 전체 렌더링에서 1/4~1/3 정도로 대폭 줄었습니다. DirectX 호출 시간은 줄일 수 없지만 나머지 CPU 작업부분들은 전부 줄일 수 있었습니다.다시한번 아이디어를 주신 최의종 팀장님께 감사의 말을 전하고 저는 이만 발표를 마치겠습니다.