42
SSE 를 를를를 를를를를 를를 를를 를 를를를 EA Seoul Studio (BFO)

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

  • Upload
    zupet

  • View
    9.500

  • Download
    6

Embed Size (px)

Citation preview

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

SSE 를 이용한 최적화와 실제 사용 예

이권일EA Seoul Studio (BFO)

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

발표 대상• C/C++ 프로그래머

• H/W 및 최적화에 관심 있는 자

• GPGPU 를 준비하는 자

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

SSE (SIMD Streaming Extension)

• 1999 년 펜티엄 3 에 처음 포함된 확장 기능

• Float Point 및 비교 로직 등 다양한 연산

• SSE 전용 128bit XMM 레지스터 8 개 추가

• MMX 와 달리 거의 모든 기능이 구현됨

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

x86/x64 레지스터

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

SIMD 연산

1.0 2.0 3.0 4.0

5.0 6.0 7.0 8.0

6.0 8.0 10.0 12.0

일반 연산

1.0

5.0

6.0

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

__m128 자료형

• SIMD 연산을 하기 위한 자료형으로 XMM 레지스터와 1:1 대응이 되는 구조체

• SSE 2 부터 새로이 추가된 __int64 와 double 을 지원하기 위한 __m128i, __m128d 자료형도 있음

• 명령어에 따라 2,4,8,16 SIMD 연산이 수행될 수 있음 . 구조체에는 어떤 데이터가 들어 있는지 알 수 없음

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;

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

시작하기 – SSE intrinsic#include "stdafx.h“#include <xmmintrin.h>

void _tmain(){

size_t count = 16 * 1024 * 1024; // 4 byte * 16M = 64MB

// C versionfloat* a = new float[count];float* b = new float[count];for(size_t i=0; i<count;++i){b[i] = a[i] + a[i];}

// SSE version__m128* a4 = (__m128*) _aligned_malloc(sizeof(float)*count, 16);__m128* b4 = (__m128*) _aligned_malloc(sizeof(float)*count, 16);for(size_t i=0; i<count/4;++i){b4[i] = _mm_add_ps(a4[i], a4[i]);}

}

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

편하게 코딩하기// 산술 연산자__forceinline __m128 operator+(__m128 l, __m128 r) { return _mm_add_ps(l,r); }__forceinline __m128 operator-(__m128 l, __m128 r) { return _mm_sub_ps(l,r); }__forceinline __m128 operator*(__m128 l, __m128 r) { return _mm_mul_ps(l,r); }__forceinline __m128 operator/(__m128 l, __m128 r) { return _mm_div_ps(l,r); }

__forceinline __m128 operator+(__m128 l, float r) { return _mm_add_ps(l,_mm_set1_ps(r)); }__forceinline __m128 operator-(__m128 l, float r) { return _mm_sub_ps(l, _mm_set1_ps(r)); }__forceinline __m128 operator*(__m128 l, float r) { return _mm_mul_ps(l, _mm_set1_ps(r)); }__forceinline __m128 operator/(__m128 l, float r) { return _mm_div_ps(l, _mm_set1_ps(r)); }

// 논리 연산자__forceinline __m128 operator&(__m128 l, __m128 r) { return _mm_and_ps(l,r); }__forceinline __m128 operator|(__m128 l, __m128 r) { return _mm_or_ps(l,r); }

// 비교 연산자__forceinline __m128 operator<(__m128 l, __m128 r) { return _mm_cmplt_ps(l,r); }__forceinline __m128 operator>(__m128 l, __m128 r) { return _mm_cmpgt_ps(l,r); }__forceinline __m128 operator<=(__m128 l, __m128 r) { return _mm_cmple_ps(l,r); }__forceinline __m128 operator>=(__m128 l, __m128 r) { return _mm_cmpge_ps(l,r); }__forceinline __m128 operator!=(__m128 l, __m128 r) { return _mm_cmpneq_ps(l,r); }__forceinline __m128 operator==(__m128 l, __m128 r) { return _mm_cmpeq_ps(l,r); }

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

SIMD 정말 4 배 빠른가요 ?

// C 버젼 for(size_t i=0; i<count;++i){

b[i] = a[i] + a[i];}-> 실행 시간 49.267 ms

// Compiler Intrinsic 버젼for(size_t i=0; i<count/4;++i){

b4[i] = a4[i] + a4[i];}-> 실행 시간 47.927 ms

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

메모리 병목 !!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]

+

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

연산량을 늘리자 ! 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_t i=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

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

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]

+ + + + +

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

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_t i=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

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

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]

+ + + +

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

a+a 과 sin() 연산 시간이 같다 ?

• C 에서 a[i] + b[i] 를 구성하는데 2.5 명령어로 실행되었고 sin() 은 19.5 명령어로 실행 (Loop Unrolling)

• SSE 에서 a4[i] + b4[i] 를 구성하는데 6 명령어로 실행되었고 sin() 은 29 명령어로 실행

a[i]+b[i] sin()0

20

40

60

80

100

120

49.267

111.273

47.927 48.939CSSE intirinsic

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

컴파일러가 최적화 안해줍니까 ?

• 컴파일러의 SSE 최적화 옵션으로 빨라질 수 있다 .

• FPU 는 구조적인 문제로 SSE 유닛보다 느리다 .

• x64 컴파일러는 FPU 를 사용하지 않고 SSE 를 기본으로 사용한다 .

• 그러나 컴파일러는 Vectorization 을 잘 못한다 .

a[i]+b[i] sin()0

20

40

60

80

100

120

49.267

111.273

49.267

71.331

47.927 48.939

CC SSE Opt.SSE intirinsic

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

더 복잡한 계산을 걸어봅시다 !

  1 3 5 7 9 11 13 15 17

C FPU 48.585 49.846 74.549 111.273 147.32 193.387 240.348 273.087 342.971

C SSE Optimize 48.998 49.253 50.862 71.331 93.255 148.949 200.072 256.95 334.526

SSE intrinsic 47.033 47.863 48.392 48.939 49.773 58.705 80.938 118.047 183.622

1 3 5 7 9 11 13 15 170

50

100

150

200

250

300

350

400

C FPU C SSE Optimize SSE intrinsic

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

몇배나 빠르다고요 ?

1 3 5 7 9 11 13 15 170

0.5

1

1.5

2

2.5

3

3.5

X 1.0 X 1.0 X 1.1

X 1.5

X 1.9

X 2.5 X 2.5

X 2.2

X 1.8

X 1.0 X 1.0

X 1.5

X 2.3

X 3.0

X 3.3

X 3.0

X 2.3

X 1.9

X 1.0 X 1.0

X 1.5 X 1.6 X 1.6

X 1.3 X 1.2

X 1.1 X 1.0

FPU / SSE Optimize FPU / intrinsic C SSE / intrinsic

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

_mm_stream_ps()

// C 버젼 for(size_t i=0; i<count;++i){

b[i] = a[i] + a[i];}-> 실행 시간 49.267 ms

// a+a stream 버젼for(size_t i=0; i<count/4;++i){

_mm_stream_ps((float*)(b4+i), _mm_add_ps(a4[i], a4[i]));}-> 실행 시간 30.114 ms

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

CPU

_mm_stream_ps() 의 작동Excution

Unit

L1 Cache

L2 Cache

Memory BUS

Memory

WC Buffer

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

_mm_stream_ps() 는 빠르다 !!

• Move Aligned Four Packed Single-FP Non Temporal

• CPU 캐쉬를 거치지 않고 WC 메모리에 데이터를 전송한다 .

• 쓰기 순서를 보장하지 않으므로 쓰고 바로 읽으면 안됨

memcpy() b4[i]=a4[i] _mm_stream_ps()0

5

10

15

20

25

30

35

40

45

50 47.17 ms 47.03 ms

29.26 ms

memcpy()

b4[i]=a4[i]

_mm_stream_ps()

0 500 1000 1500 2000 2500 3000 3500 4000 4500 5000

2,713.6 MB/s

2,721.5 MB/s

4,374.1 MB/s

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

그렇다면 sin() 도 빨라질까 ?// SSE intrinsicfor(size_t i=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 intrinsic + _mm_stream_ps()for(size_t i=0; i<count/4; ++i){

_mm_stream_ps( (float*)(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 );

}-> 실행 시간 32.081 ms

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

Stream 을 추가한 그래프 !!

  1 3 5 7 9 11 13 15 17C FPU 48.585 49.846 74.549 111.273 147.32 193.387 240.348 273.087 342.971

C SSE Optimize 48.998 49.253 50.862 71.331 93.255 148.949 200.072 256.95 334.526SSE intrinsic 47.033 47.863 48.392 48.939 49.773 58.705 80.938 118.047 183.622

SSSE intrinsic+stream

29.263 29.83 30.636 32.081 39.067 56.332 80.373 117.28 183.932

1 3 5 7 9 11 13 15 170

50

100

150

200

250

300

350

400

C FPU C SSE Optimize SSE intrinsic SSSE intrinsic+stream

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

같은 시간에 더 많은 일을 합시다 !!

• float Read + Write 시간 : 2.896 ns• __m128 Read + Write 시간 : 11.214 ns• __m128 Read + Stream 시간 : 6.977 ns

  1 3 5 7 9 11 13 15 17

C FPU48.585 49.846 74.549

111.273

147.32193.38

7240.34

8273.08

7342.97

12.5 8.5 13.5 19.5 25.75 32 39 43 49

C SSE Optimize48.998 49.253 50.862 71.331 93.255

148.949

200.072

256.95334.52

62.5 7.5 13.625 18.5 25.875 31 36 41 47

intrinsic47.033 47.863 48.392 48.939 49.773 58.705 80.938

118.047

183.622

5 11 19 29 41 55 71 91 114

intrinsic+stream29.263 29.83 30.636 32.081 39.067 56.332 80.373 117.28

183.932

5 11 19 29 41 55 71 91 113

Hand Opt.30.251 30.403 31.047 32.554 32.71 36.161 45.853 64.715 97.201

8 12 16 20 24 28 32 36 40

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

SSE 프로그래밍• 메모리 접근 시간이 길어지고 연산 시간이

짧아짐에 따라 더 많은 계산을 할 수 있다 .

• 요즘 CPU 는 Out-of-Order 로 인해 대부분 비동기 실행을 한다 . 적극 이용하자 .

• 병렬화와 병목 문제는 GPGPU 연산에도 동일하게 적용된다 . 미래를 대비하자 .!!

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

SSE 를 적용한 예제들

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

SSE 를 사용한 CPU Skinning

• Vertex : 1024 * 1024

• Bone : 200

• 4 weight per vertex + normal + tangent

• SSE 컴파일 옵션이 켜진 C, SSE 최적화

• 스키닝 없는 C 루프 복사 , SSE 루프 복사 , mem-cpy()

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

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;

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

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 );

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

SSE Skinning 결과

• memcpy() 시간의 80% 로 스키닝을 할 수 있다 .

• 파티클 , UI 등에 유용하게 사용할 수있다 .

• Dynamic VB 를 쓰는 동안 계산을 추가로 할 수 있다 .

C++ Skinning SSE Skinning C++ Copy SSE Copy memcpy 0

10

20

30

40

50

60

70

8070.648972

27.648912

34.219357

26.265869

34.247543

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

SSE 를 사용한 KdTree

• Ray-Trace 에 특화된 Binary Tree (Axis Aligned BSP)

• Deep-Narrow Tree 를 만들어야 효율이 좋아지므로 노드가 무척 많아진다 .

• Tree Node 방문이 전체 처리 시간의 90% 을 차지한다 .

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

kDTree Traverse

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

kDTree Packet Traverse

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

KdTree 테스트 결과

left node visit right node visit total node visit total leaf visit triangle intersect

  Compiler Opt. SSE Intrinsic Ratio

실행시간 0.1564772 0.075881 2.0621

MRays/Sec 4.09 8.43 0.4852

call 640000 160000 4.0000

left node visit 6932440 1940425 3.5726

right node visit 6017289 1686695 3.5675

total node visit 13589729 3787120 3.5884

total leaf visit 17360227 5092768 3.4088

triangle intersect 608505 238760 2.5486

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

Scaleform 과 SSE

• Flash 파일을 3D 가속을 받으며 실행 가능하도록 만들어진 라이브러리

• Direct3D/OpenGL 및 다양한 렌더링 라이브러리 지원

• 현재 프로젝트의 UI 제작에 사용

• 209 개 파일 65147 Line 의 Acton Script 와 DXT5 79MB UI 이미지

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

Scaleform 3.1 의 문제점 • 복잡한 swf 들을 다수 사용할 경우 CPU

사용률이 상당히 높다 .

• 높은 자유도가 GPU 에 최적화 되기 어려운 UI 를 만들게 한다 .

• GRendererD3D9 은 예제 코드에 가깝고 개발시 H/W 특성이 고려되지 않았다 .

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

5~15ms/frame

Scaleform 개선 방향Client GFx

GFxMoveView::Advance()

GFxMoveView::Display()

ID3DDevice::DrawPrim()

SceneMgr::DrawScene()

Client GFx GFxQueue

GFxMoveView::Advance()

Direct3D

GFxMoveView::DisplayMT()

GFxQueue::DrawPrim()

SceneMgr::DrawScene()

GFxQueue::Flush() ID3DDevice::DrawPrim()

Direct3D

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

GFxQueue 의 Batch 합치기 기능

• Batch 합치기를 하기 위해 Vertex 를 Queue 에 넣을때 Transform (TnL) 을 미리 처리

• Render State, Texture State 를 체크해서 중복된 렌더링 재설정을 방지

• Scene 에서 벗어난 Shape 들 안그리는 기능 추가

• CPU 로 대체된 VertexShader 는 삭제 , Pixel Shader 도 Batch 합치기를 위해 수정

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

Transform 코드case VS_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 + ver-texShaderConstant[2].x) * g_width;

//output->pos.y = g_y - (input->x * vertexShaderConstant[0].y + input->y * vertexShaderConstant[1].y + ver-texShaderConstant[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 + vertexShader-

Constant[5].x;//output->tc0.y = input->x * vertexShaderConstant[3].y + input->y * vertexShaderConstant[4].y + vertexShader-

Constant[5].y;//aabb.AddPoint(output->pos);

__m128 pos = g_pos + ( input->x*vertexShaderConstant[0] + input->y*vertexShaderConstant[1] + vertexShader-Constant[2] ) * g_size;

_mm_storeu_ps(output->pos, pos);__m128i colors = _mm_loadl_epi64((__m128i*)&input->color);__m128i unpack = _mm_unpacklo_epi8(colors, g_zero);__m128i shuffle = _mm_shufflelo_epi16(unpack, _MM_SHUFFLE(3,0,1,2));shuffle = _mm_shufflehi_epi16(shuffle, _MM_SHUFFLE(3,0,1,2));__m128i packed = _mm_packus_epi16(shuffle, g_zero);_mm_storel_epi64((__m128i*)&output->color, packed); __m128 tc = 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;

}}

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

GFxQueue Draw Call 횟수

0

100

200

300

400

500

600

700

800

GFxGFxQueued

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

GFx Renderer 코멘트• GRenderD3D9 코드가 구리다 . 프로그래머라면 찬찬히

분석한다음 여러군데 손을 봐두자 .

• UI 아티스트는 GPU 최적화에 신경쓰지 않는다 . 초기 단게부터 적절한 레이아웃과 컴포넌트를 설계해두자 .

• GFxExport 에서 DXTn 포맷을 무조건 2 의 배수로 Resize 해버려 저장하는 경우가 있다 .

• GFxExport 에서 Texture Atlas 기능을 쓰는 것도 최적화에 큰 도움이 된다 .

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

?