[0204 구경원] sse 병렬 프로그래밍
DESCRIPTION
TRANSCRIPT
SSE 병렬 프로그래밍
데브루키돼지고기 ( 구경원 )
SIMD.
• Single Instruction Multiple Data• CPU 에서 지원하는 일종의 명령어 셋 .• 한번의 연산으로 다수의 데이터를 처리할 수
있다 .
• SISD - Single Instruction Single Data• SIMD - Single Instruction Multiple Data• MISD - Multiple Instruction Single Data• MIMD - Multiple Instruction Multiple Data
SIMD.
Input Data Output Data
SISD 명령어
Input Data Output Data
SIMD 명령어
SIMD.
… … 11 10 v 8 7 6 5 4 3 2 1
4 3 2 1
8 7 6 5
• SISD
• SIMD 연산의 방향32 bit 32 bit 32 bit 32 bit
4 3 2 1
+ + + +
8 7 6 5
+ + + +
12 11 10 9
+ + + +
16 15 14 13
= = = =
40 36 32 28
4 = 136값의 합
• 어셈블리 SIMD 명령어 • Intrinsic 함수• Vector Class
SIMD 구현 방법 .
SIMD 명령어 Intrinsic 함수 Vector Class
xmm0 __m128i Is16vec8
xmm1 __m128 Is32vec4 ~
~ __m128d F32vec4
xmm7 F64vec2
SIMD 구현 방법 .
• 프로젝트의 중요 부분 또는 병목 지점인가 .• SIMD 구조에 적합한가 .• 성능향상에 도움이 되어지는가 .• 정수형 , 실수형 인지 파악 .• 128bit 에 담을 수 있는 데이터 개수 고려 .• 제작기간 , 디버깅 테스트 기간 고려 .• 구현 도구 결정 .• SISD 와 SIMD 성능 비교 테스트 .
SIMD 구현 조건 .
SSE.
• Streaming SIMD Extensions• XMM 128 비트 레지스터 8 개가 존재 .• 인텔이 1999 년 펜티엄 3 프로세서에 도입 .• FLOAT, POINT. 비교로직 등 다양한 연산
가능 .
Packing 사이즈 byte short integer
병렬 연산 개수 16 8 4
C 코드와 성능 차이 4~6 배 2 배 10~30%
CPU 구현 프로세서 .
IA-32 / 64 레지스터
32 bit A3 32 bit A2 32 bit A1 32 bit A0
Scalar 덧셈 계산 +
32 bit B3 32 bit B2 32 bit B1 32 bit B0
=
32 bit A3 32 bit A2 32 bit A1 32 bit A0+B0
32 bit A3 32 bit A2 32 bit A1 32 bit A0
Packed 덧셈 계산 + + + +
32 bit B3 32 bit B2 32 bit B1 32 bit B0
= = = =
32 bit A3+B3 32 bit A2+B2 32 bit A1+B1 32 bit A0+B0
SIMD 연산 타입
중요 어셈블리
• mov • add / sub• mul / div• inc / dec
• shl / shr• cmp / jp
어셈블리 예제 .
__asm{
pushadmov eax, Amov ebx, Badd eax, ebxmov C, eaxpopad
}
어셈블리 예제 .
__asm{
pushadmov ebx, 15mov eax, Amul ebxmov C, eaxpopad
}
어셈블리 예제 .
__asm{
pushad
mov eax, 17cdq //32 bit 를 64 bit 로 확장//convert double word to quad word
mov ebx, Adiv ebxmov B, eax
mov C, edx
popad}
어셈블리 예제 .
__asm{
pushad
mov eax, 0LOOP:
// 필요한 연산
inc eaxcmp eax, 1000jne LOOP
mov nValue, eaxpopad
}
SIMD 명령어
• 정수형과 실수형 두 가지 병렬 연산 방식이 있다 .• Pack 형식에 따라 연산 방식이 달라진다 .• MMX 에서는 64bit 병렬 연산만 가능했지만 SSE
로 넘어오면서 128bit 병렬 연산이 가능해졌다 .• 구현코드가 CPU 에서 똑같이 동작한다 .• 디버깅이 어렵고 , 가독성이 안좋다 .
명명법
P <SIMD_op> <suffix>
접미사 원 어 사이즈 의 미
S signed - 양수값을 의미하는 접미사로 사이즈를 의미하는 단어 앞에 오게 된다 .
U unsigned - +- 부호를 갖는 데이터형임을 의미한다 .
B Byte 8 bit 해당 데이터는 8 bit 정수 형 16 개를 연산할 수 있다 .
W Word 16 bit 해당 데이터는 16 bit 정수 형 8 개를 연산할 수 있다 .
D DoubleWord 32 bit 해당 데이터는 32 bit 정수 형 4 개를 연산할 수 있다 .
Q QuadWord 64 bit 해당 데이터는 64 bit 정수 형 2 개를 연산할 수 있다 .
• 정렬된 메모리를 사용하는 것이 빠르다 .
• 정렬된 메모리• __declspec( align(16) ) int array[100];
• 정렬되지 않은 메모리• Int array[100]
메모리 align, unalign
MOVDQU Move Unaligned Double Quad word
• 128bit 레지스트 또는 128bit 메모리에 정렬되지 않은 값을 읽어올 때 사용한다
16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit
16 Byte Unaligned Memory 8 7 6 5 4 3 2 1
MOVDQU 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit
xmm0 레지스터 8 7 6 5 4 3 2 1
• 128bit 레지스터 또는 128bit 메모리에 정렬되어 있는 값을 읽어올 때 사용 한다 .
• 메모리가 정렬되어 있기 때문에 읽어오는 속도가 빠르다 .
MOVDQA Move aligned Double Quad word
16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit
16 Byte Aligned Memory 8 7 6 5 4 3 2 1
MOVDQA 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit
xmm0 레지스터 8 7 6 5 4 3 2 1
논리연산
32 bit 32 bit 32 bit 32 bit
SourceA 1 1 0 0
SourceB 1 0 1 0
PAND 1 0 0 0
POR 1 1 1 0
PXOR 0 1 1 0
PANDN 0 0 1 0
PADDD (Packed Add)
32 bit 32 bit 32 bit 32 bit
xmm0 4 3 2 1
paddd + + + +
xmm1 8 7 6 5
= = = =
xmm0 12 10 8 6
• 더하기 연산을 한다
PADDD (Packed Add)
int IntArrayA[4] = { 1,2,3,4 };int IntArrayB[4] = { 5,6,7,8 };int IntResult[4] = {0};
__asm{
pushad
movdqu xmm0, IntArrayAmovdqu xmm1, IntArrayBpaddd xmm0, xmm1movdqu IntResult, xmm0
popademms
}
… … 11 10 v 8 7 6 5 4 3 2 1
4 3 2 1
8 7 6 5
• SISD
• SIMD 연산의 방향32 bit 32 bit 32 bit 32 bit
4 3 2 1
+ + + +
8 7 6 5
+ + + +
12 11 10 9
+ + + +
16 15 14 13
= = = =
40 36 32 28
4 = 136값의 합
PADDD (Packed Add)
최대값 구하기
short GetMaxValueC(const short *pShortArray,const int nSize) { short MaxValue = 0;
for(int i = 0; i< nSize ; i++){
if(pShortArray[i] > MaxValue )MaxValue = pShortArray[i];
}return MaxValue;
}
최대값 구하기
const short* pShort = pShortArray;// 스택포인터변수로받아온다 .
int nLoopCount = (nSize / 8)*16;//8 배수개수를한번에계산
int nRemain = nSize % 8;//8 배수나머지영역은 C 로구현
short nMaxValue = 0;// 결과값을가지고있을메모리변수
최대값 구하기• __asm• {• pushad• mov eax, pShort• mov esi, 0•
• mov ecx, nLoopCount• pxor xmm1, xmm1• FINDLP:• movdqu xmm0, [eax+esi] .
• pmaxsw xmm1, xmm0•
• add esi, 16 .• cmp esi, ecx• jne FINDLP
• movdqu MaxValueArray, xmm1• popad• emms• }
최대값 구하기• for( unsigned char Count = 0; Count < 8 ; Count++)• {• if( MaxValueArray[Count] > nMaxValue )• nMaxValue = MaxValueArray[Count];• }•
• if( nRemain != 0)• {• for( int i = nSize-nRemain; i< nSize; i++)• {• if(pShortArray[i] > nMaxValue )• nMaxValue = pShortArray[i];• }• }
GetLength()f = sqrtf(x_ * x_ + y_ * y_ + z_ * z_);
• FLOAT* p = &f; w_ = 0.0f;
• __asm {– mov ecx, p– mov esi, this – movups xmm0, [esi] – mulps xmm0, xmm0 – movaps xmm1, xmm0 – shufps xmm1, xmm1, 01001110b – addps xmm0, xmm1– movaps xmm1, xmm0 – shufps xmm1, xmm1, 00010001b– addps xmm0, xmm1– sqrtss xmm0, xmm0 – movss [ecx], xmm0
• }
최대값 구하기
• 코드의 분석이 쉽다 .• SIMD 명령어를 inline 함수로 구현하여 , 함수의
성능은 SIMD 명령어 셋과 차이가 별로 없다 .• Scalar 형 intrinsic 함수는 어셈블리 SIMD
명령어 보다 약간의 성능저하가 있을 수 있다 .• SIMD 명령어 보다 코드 작성에 편리하다 .
Intrinsic 함수
명명법
_mm_<intrin_op>_<suffix>
문자 데이터 타입
s 32bit 실수형 (single-precision floating point)
d 64bit 실수형 (double-precision floating point)
i128 128bit signed 정수 (signed 128-bit integer)
i64 64bit signed 정수 (signed 64-bit integer)
u64 64bit unsigned 정수 (unsigned 64-bit integer)
i32 32bit signed 정수 (signed 32-bit integer)
u32 32bit unsigned 정수 (unsigned 32-bit integer)
i16 16bit signed 정수 (signed 16-bit integer)
u16 16bit unsigned 정수 (unsigned 16-bit integer)
i8 8bit signed 정수 (signed 8-bit integer)
u8 8bit unsigned 정수 (unsigned 8-bit integer)
• 헤더 파일 include• - #include <xmmintrin.h> SSE
- #include <emmintrin.h> SSE2- #include <pmmintrin.h> SSE3- #include <smmintrin.h> <nmmintrin.h> SSE4
Intrinsic 함수
• SIMD 연산을 위한 자료형으로 XMM 레지스트와 1 대 1 대응되는 구조체이다 .
__m128 자료형
___m128i 32 bit integer 32 bit integer 32 bit integer 32 bit integer
Intrinsic LOAD. STORE
__m128i r = _mm_load_si128(__m128i const*p)
16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit
16Byte Align Memory p p[7] p[6] p[5] p[4] p[3] p[2] p[1] p[0]
__m128i r r[7] r[6] r[5] r[4] r[3] r[2] r[1] r[0]
void _mm_store_si128(__m128i *p, __m128i b)
16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit 16 bit
__m128i b b[7] b[6] b[5] b[4] b[3] b[2] b[1] b[0]
16Byte Align Memory p p[7] p[6] p[5] p[4] p[3] p[2] p[1] p[0]
Intrinsic 함수
short Source[8] = {1,2,3,4,5,6,7,8};short Dest[8] = {0};
__m128i xmm0 = _mm_loadu_si128((__m128i*)Source);__m128i xmm1 = xmm0;
_mm_storeu_si128((__m128i*)Dest, xmm1);
Intrinsic ADD. SUB
__m128i R = _mm_add_epi16(__m128i a, __m128i b)
__m128i a a7 a6 a5 a4 a3 a2 a1 a0
+ + + + + + + +
__m128i b b7 b6 b5 b4 b3 b2 b1 b0
= = = = = = = =
__m128i r a7+b7 a6+b6 a5+b5 a4+b4 a3+b3 a2+b2 a1+b1 a0+b0
__m128i R = _mm_sub_epi16(__m128i a, __m128i b)
__m128i a a7 a6 a5 a4 a3 a2 a1 a0
- - - - - - - -
__m128i b b7 b6 b5 b4 b3 b2 b1 b0
= = = = = = = =
__m128i r a7-b7 a6-b6 a5-b5 a4-b4 a3-b3 a2-b2 a1-b1 a0-b0
Intrinsic MUL
__m128i R = _mm_mullo_epi16(__m128i a, __m128i b)
__m128i a a7 a6 a5 a4 a3 a2 a1 a0
* * * * * * * *
__m128i b b7 b6 b5 b4 b3 b2 b1 b0
= = = = = = = =
__m128i r a7*b7 a6*b6 a5*b5 a4*b4 a3*b3 a2*b2 a1*b1 a0*b0
Intrinsic MAX. MIN
__m128i R = _mm_max_epi16(__m128i a, __m128i b)
__m128i a a7 a6 a5 a4 a3 a2 a1 a0
max max max max max max max max
__m128i b b7 b6 b5 b4 b3 b2 b1 b0
= = = = = = = =
__m128i r max(a7,b7) max(a6,b6) max(a5,b5) max(a4,b4) max(a3,b3) max(a2,b2) max(a1,b1) max(a0,b0)
__m128i R = _mm_min_epi16(__m128i a, __m128i b)__m128i a a7 a6 a5 a4 a3 a2 a1 a0
min min min min min min min min
__m128i b b7 b6 b5 b4 b3 b2 b1 b0
= = = = = = = =
__m128i rmin(a7,b7
)min(a6,b6
)min(a5,b5
)min(a4,b4
)min(a3,b3) min(a2,b2) min(a1,b1) min(a0,b0)
최대값 구하기
const short* pShort = pShortArray;int nRemain = nSize % 8;short nMaxValue = 0;short MaxValueArray[8] ={0};
__m128i XMMCurrentValue;__m128i XMMMaxValue;
for(unsigned int Index =0 ; Index < nSize; Index+=8){
XMMCurrentValue = _mm_loadu_si128((__m128i*)(pShortArray+Index)); //16byte 씩읽어온다 .
XMMMaxValue = _mm_max_epi16(XMMMaxValue, XMMCurrent-Value); //16byte 씩더한다 .}
최대값 구하기
Vector 클래스
• Intrinsic 데이터형 또는 함수를 클래스화시킨 라이브러리 .
• Intrinsic 를 이용할 때 보다 직관적이고 사용이 편리하다 .
• 연산자 오버로딩으로 만들어져 있다 .
• 명명법– <type><signedness><bits>vec<elements>
Iu32vec432bit unsigned int 형 정수를 4 개 담고 있는 vector 클래스 .
Fs16vec88bit signed short 형 실수를 8 개 담고 있는 vector 클래스 .
Vector 클래스
Vector 클래스class 부호 pack 데이터 타입 pack 사이즈 pack 개수 해더 파일
I128vec1 unspecified __m128i 128 1 dvec.h
I64vec2 unspecified __int64 64 2 dvec.h
Is64vec2 signed __int64 64 2 dvec.h
Iu64vec2 unsigned __int64 64 2 dvec.h
I32vec4 unspecified int 32 4 dvec.h
Is32vec4 signed int 32 4 dvec.h
Iu32vec4 unsigned int 32 4 dvec.h
I16vec8 unspecified short 16 8 dvec.h
Is16vec8 signed short 16 8 dvec.h
Iu16vec8 unsigned short 16 8 dvec.h
I8vec16 unspecified char 8 16 dvec.h
Is8vec16 signed char 8 16 dvec.h
Iu8vec16 unsigned char 8 16 dvec.h
Vector 클래스
__asm{movaps xmm0, amovaps xmm1, baddps xmm0, xmm1movaps c, xmm0}
#include < xmmintrin.h >__m128 a, b, c;c = _mm_add_ps( a, b);
#include <fvec.h>F32vec4 A, B, CC = A + B;
Vector 클래스 읽고 . 쓰기__declspec(align(16)) short A[8] = {1,2,3,4,5,6,7,8};__declspec(align(16)) short R[8] = {0};
Is16vec8 Vector(1,2,3,4,5,6,7,8); // 역순으로 8 부터 입력
_mm_store_si128((__m128i *)R, Vector); //intrinsic 함수로 쓰기
printf("Store : %d, %d, %d, %d, %d, %d, %d, %d\n",R[0],R[1],R[2],R[3],R[4],R[5],R[6],R[7]);
Is16vec8 *a = (Is16vec8 *)A; // 포인터 캐스팅으로 바로 읽기
Is16vec8 *r = (Is16vec8 *)R;
*r = *a; // 대입 연산
printf("Store : %d, %d, %d, %d, %d, %d, %d, %d\n",R[0],R[1],R[2],R[3],R[4],R[5],R[6],R[7]);
return 0;
Vector 클래스 사칙연산
Is16vec8 A;Is16vec8 B;Is16vec8 R;R = A + B; // 덧셈 연산R += A;R = A - B; // 뺄셈 연산R -= A;R = A * B; // 곱셈 연산R *= A;R = mul_high( A, B ); // 곱셈 상위 bitR = mul_add( A, B); // 곱 합 연산
최대값 구하기
const short* pShort = pShortArray;int nRemain = nSize % 8;short nMaxValue = 0;short MaxValueArray[8] ={0};
Is16vec8* XMMCurrentValue;Is16vec8 XMMMaxValue;
for(unsigned int Index =0 ; Index < nSize; Index+=8){
XMMCurrentValue = (Is16vec8*)(pShortArray+Index);XMMMaxValue = simd_max(XMMMaxValue,* XMMCurrent-
Value);}
최대값 구하기
마무리
- SSE 를 사용하면 C/C++ 보다 빠른 성능 향상을 볼 수 있다 .- 멀티코어 시대에 SSE 의 사용은 미래를 대비하는 일이다 .- 아직까지 가독성의 문제가 남아있다 .- 반복되는 계산량이 많은 부분에서 많은 이득을 볼 수 있다 .- 캐쉬라인 정렬이 되어있어야 효율적이다 .
Q&A
END