
요즘 너무 바뻐서 블로그 포스팅을 하지 못하고 있습니다.
올해 목표가 '제발 일좀 벌리지 말자' 였는데 2년 동안 생고생할 일을 벌리고 말았다는..
대박 사고 치고 말았네요. ㅠ.ㅠ
공부하고 싶은 이슈들이 정말 많았는데..쩝
이왕 이렇게 된것 끝장을 볼때까지 '정주행!'

CPU의 코어가 늘어나면서 정말로 수많은 병렬프로그래밍 방법이 나오고 있습니다.
이전에는 각 벤더들 마다 제시하는 스레드를 생성하는 API가 달라서 표준화 되었으면 좋겠다는 생각이 들었는데
이제는 한걸음 더 나아가 병렬 프로그래밍 방법들이 벤더들 마다 나와서 사람들을 혼란스럽게 만들고 있습니다.
GPGPU 까지 가세를 해서 이기종 컴퓨팅이니 멀티코어니 이름만해도 해아리기 힘들 정도 입니다.
SIMD, OpenMP, MPI, CUDA, OpenCL, C++ AMP, Cilk Plus, TBB...
마이너한 놈들은 제외하고 주요한 방법들을 나열해도 이렇게 많습니다.
어떤 개발자가 저에게 빨리 똑똑한 놈 하나가 평정했으면 좋겠다고 하는데 공감되는 부분도 있습니다.
또 어떤 모임에서는 서로 사용하는 방법 나중에 득세할 것이라고 언쟁을 높이는 모습도 보았습니다.
그 싸움은 꼭 벤더들을 대변하는 모습처럼 보였습니다.
MS vs Intel vs AMS,Apple vs NVidia
꼭 DSLR 사용자가 캐논과 니콘 이라는 회사를 대변하는 것처럼
자동차 유저가 현대, 기아, GM, SM을 대변하는 것처럼..
흥미로운 현상들입니다.
각자 다 일리가 있는 말들을 합니다.
이것이 표준안이다. 또는 어떤 회사가 더 시장장악력이 높다.
한가지 방법을 배우는데 많은 시간을 투자하기 때문에 상대 진영이 득세를 하게 되면
자신이 배운 방법이 헛수고가 되기 때문에 더 집착하는 것 일 수도 있습니다.
하지만 한가지 방법을 배우고 나서, 다른 방법을 배우면 보다 쉽게 알 수 있고
처음 투자한 시간의 20~30%만 들이면 마스터 할 수 있습니다.
그렇게 되면 사람의 뇌도 병렬화에 익숙해 지고
이 방법이나 저 방법이나 다 똑같아 보입니다.
보다 유리한 방법을 적용하면 되는 것이고 소모적인 싸움은 할 필요가 없습니다.
한가지 방법에 올인하는 것 보다 병렬화를 이해하면 모든 것이 해결될 것으로 보입니다.
컴퓨팅의 근본은 다 거기서 거기라는 걸...
4장 AVX 프로그래밍-ml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />-ml:namespace prefix = o />-ml:namespace prefix = o />-ml:namespace prefix = o />
예제 프로그램을 통해 자세히 알아보자.
4-1. AVX 어셈블리 명령어 (Instruction Set)
AVX 명령어는 정수형, 실수형 모두 3항 연산으로 변화되었고, 실수형 계산에 대해서는 256bit ymm 레지스터를 사용할 수 있다.
명명법
기존 SSE 명령어에 V 접두사의 추가되어 일관되고 쉽게 AVX 명령어를 사용할 수 있다.
표 4-1) SSE와 AVX 명령어 (Instruction Set) 차이점
명령어 | SSE | AVX | 변경점 |
정수 데이터 이동 | movdqu xmm0, [eax+esi] | vmovdqu xmm0, [eax+esi] | - |
정수형 곱셈 | pmullw xmm0, xmm1 | vpmullw xmm2, xmm0, xmm1 | 3항연산 |
정수형 뺄셈 | psubw xmm2, xmm0 | vpsubw xmm2, xmm2, xmm0 | 3항연산 |
정수형 덧셈 | paddw xmm2, xmm1 | vpaddw xmm2, xmm2, xmm1 | 3항연산 |
실수 데이터 이동 | movups xmm0, [eax+esi] | vmovups ymm0, [eax+esi] | 256bit 레지스터 |
실수형 덧셈 | addps xmm0, xmm1 | vaddps ymm2, ymm1, ymm0 | 256bit 레지스터 |
실수형 곱셈 | mulps xmm0, xmm1 | vmulps ymm2, ymm1, ymm0 | 256bit 레지스터 |
AVX 명령어를 이용하여 정수형 두 벡터의 합을 계산하는 코드를 작성해 보자.
예제 4-1) AVX를 이용한 short형 두 벡터의 합 함수 (어셈블리 코드)
void AddVectorAVX(short* DataA, short* DataB, short* DataC, int Size) { int nLoopCount = (Size/8)*16; __asm { mov eax, DataA mov ebx, DataB mov ecx, DataC mov edx, nLoopCount mov esi, 0 pxor xmm2, xmm2 SIMDLOOP: vmovdqu xmm0, [eax+esi] vmovdqu xmm1, [ebx+esi]
vpaddw xmm2, xmm1, xmm0 vmovdqu [ecx+esi], xmm2
add esi, 16 cmp esi, edx jne SIMDLOOP } } |
주: 예제 코드의 간결성을 위해 사용되는 배열의 크기는 8의 배수로 고정한다.
기존 SSE를 이용한 코드에서 합 계산은 다음과 같이 작성했다.
paddw xmm0, xmm1 movdqu [ecx+esi], xmm0 |
마치 모든 연산이 C의 단항 연산 R+=A; 와 같은 코드와 같이 작성되었다.
변경된 AVX 명령어는 R = A+B; 와 같은 형태로 변경되어 코드를 작성하기가 휠씬 수훨해졌다.
vpaddw xmm0, xmm1, xmm2 vmovdqu [ecx+esi], xmm2 |
이러한 코드의 변화는 SSE에서는 source의 값이 사라져서 별도의 레지스터에 값을 보관하는 코드를 부가적으로 작성해야 했지만, AVX는 불필요한 작업을 하지 않아도 된다.
이것은 불필요한 코드가 사라지고 성능의 차이로 나타나게 된다.
R= A*B + A - B 과 같은 연산에서 발생하게 되는 부가적인 코드 살펴보자.
예제 4-2) SSE와 AVX를 이용한 R= A*B + A - B연산
SSE | AVX |
movdqu xmm0, [eax+esi] movdqu xmm1, [ebx+esi] movdqu xmm2, xmm0 ;부가적인 코드 movdqu xmm3, xmm1 ;부가적인 코드 pmullw xmm0, xmm1 psubw xmm0, xmm2 paddw xmm0, xmm1 movdqu [ecx+esi], xmm0 | vmovdqu xmm0, [eax+esi] vmovdqu xmm1, [ebx+esi] vpmullw xmm2, xmm0, xmm1 vpsubw xmm2, xmm2, xmm0 vpaddw xmm2, xmm2, xmm1 vmovdqu [ecx+esi], xmm2 |
예제 4-2)에서 SSE는 단항연산으로 인해 source의 값이 사라지는 것을 방지하기 위해 별도의 레지스터 xmm2와 xmm3에 값을 저장하는 코드가 추가 되었다.
하지만 AVX는 이항연산을 하기 때문에 source의 값이 사라지지 않고 간결하게 코드를 작성할 수 있다.
주의 사항
정수형 연산에서 SSE 명령어와 AVX 명령어를 혼합해서 사용하면 속도가 저하 될 가능성이 있다.
SSE 명령에 대한 Instructions Set 집합과 AVX 명령에 대한 Instructions Set 집합이 다르기 때문에 혼합하여 사용하면 Instructions Set 집합을 다시 로드하여 부하가 발생할 수 있다.
4-2. Intrinsic 함수 사용
Intrinsic 함수는 SSE 버전에서도 3항 연산을 지원하여 정수형 Intrinsic 함수는 AVX에서 새로 제공하는 것이 거의 없다. 따라서 Intrinsic 함수는 실수형에 대해서만 의미가 있다.
정수형에 대해서는 기존에 사용하는 128bit형 Intrinsic 함수를 그대로 사용하면 된다.
AVX Intrinsic 함수를 사용하기 위해서는 다음 해더 파일을 포함해야 한다.
#include "immintrin.h" |
실수형 AVX Intrinsic 함수
데이터형 추가 : __m256
기존 실수형 128bit 데이터형 __m128이 256bit로 확장된 __m256이 추가되었다.
명명법
접두사 : _mm256
SSE 덧셈 함수를 예로 들면 _mm_add_ps의 _mm_ 접두사가 _mm256_add_ps 처럼 함수의 이름이 변화되었다.
예제 코드 4-3) AVX Intrinsic 함수를 이용한 배열의 총합 계산 함수 (C코드)
float calcSumAVX(float* pData, int size) { int i = 0; float sum = 0; float * pFloatA = pData; __m256 avxSum = _mm256_setzero_ps(); __m256 avxCurValue = _mm256_setzero_ps();
for( i = 0; i < size; i += 8) { avxCurValue = _mm256_loadu_ps(pFloatA+i); avxSum = _mm256_add_ps(avxSum, avxCurValue ); } for( i = 0; i < 8; i++) { sum += *((float*)(&avxSum)+i); }
return sum; } |
주: 예제 코드의 간결성을 위해 사용되는 배열의 크기는 8의 배수로 고정한다.
5장 AVX 성능 테스트
AVX와 SSE의 성능 테스트를 통해 그 효과를 알아보자.
5-1. AVX vs SSE (float 총합계산 속도 비교)
C이용한 float 연산과 128bit xmm 레지스터에 4개 float 형을 연산하는 SSE, 256bit ymm 레지스터에 8개 float 형을 연산을 AVX의 성능 차이를 직접 확인해보자..
AVX를 이용하여 float 배열의 총합 계산을 하는 예제 4-3)과 동일한 기능을 하는 함수를 C와 SSE로 작성한다.
예제 5-1) C를 이용한 총합계산 코드
float calcC(float* pData, int Size) { int i = 0; float sum = 0;
for( i = 0; i < Size; i++ ) { sum += pData[i]; }
return sum; } |
예제 5-2) SSE를 이용한 총합계산 코드 (C코드)
float CalcSumSSE (float* pData, int size) { int i = 0; float sum = 0; float * pFloatA = pData; __m128 sseSum = _mm_setzero_ps(); __m128 sseCurValue = _mm_setzero_ps();
for( i = 0; i < size; i += 4) { sseCurValue = _mm_loadu_ps(pFloatA+i); sseSum = _mm_add_ps( sseSum, sseCurValue ); } for( i = 0; i < 4; i++) { sum += *((float*)(&sseSum)+i); }
return sum; } |
주: 예제 코드의 간결성을 위해 사용되는 배열의 크기는 8의 배수로 고정한다.
260,000개의 float 배열의 총합 계산에 대해 예제 4-2)과 예제 5-1)의 코드를 이용하여 실행한 뒤 그 처리 시간을 비교하였다.

-ml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />-ml:namespace prefix = v />-ml:namespace prefix = v />-ml:namespace prefix = v />
그림 5-1) 총합 계산 처리 속도 비교
C vs AVX의 성능 비교 실험에서는 AVX가 C에 비해 약 6.5배 빠른 속도를 내고,
SSE vs AVX의 성능 비교 실험에서 AVX가 약 62% 빠른 속도를 내는 것을 알 수 있다.
이 결과를 그래프로 그려 보면 그 차이를 확연히 알 수 있다.

그림 5-2) 실험 1 테스트 결과 (단위: msec)
5-2 테스트 환경
CPU : Intel sandy bridge i7-2630QM
OS : Windowns 7 OS 64bit sp1
Tool : Visual Studio 2010 sp1
SSE, AVX intrinsic 사용
64bit Release 버전실행 파일
6장 결론
2004년 이후 CPU는 전력소모와 발열의 벽으로 인해 클록속도 경쟁을 포기하게 되고, CPU 제조사는 두가지 중요한 노선을 결정하게 된다.
1. 멀티코어
2. SIMD
이로 인해 병렬처리로 작성되지 않는 프로그램은 CPU 발전의 혜택을 더 이상 받을 수 없다. 이제 경쟁력 있는 프로그램을 작성하기 위해서는 멀티코어와 SIMD 지원은 필수이다.
하드웨어가 발전할 수록 그 격차는 더욱 커질 수 밖에 없다.
모든 일에 있어서 직업은 자신의 적성과 맞아야 보람과 능력을 발휘할 수 있다.
나는 프로그래머라는 직업이 나의 적성과 잘 맞아서 참 감사하게 생각한다.
나의 천직을 찾아 성과를 낼 수 있으니 행복하지 않을 수 없다.
완벽한 상황이지만 한국에서.. 라는 환경 조건이 붙으면 약간을 달라진다.
많은 개발자들이 박봉에 야근하며 시달리는 얘기들을 많이 하지만
오늘은 그런 얘기를 하려고 하는 것이 아니다.
프로그래머들 간에 잘못된 습성과 그로 인해 받는 마음의 상처를 최소화하는 방법을 얘기하려고 한다.
10년간 프로그래머로 있으면서 주위에서 개발자로 살아남은 동료들을 보면 비슷한 성격을 지니고 있다.
프로그래밍 실력에 대한 프라이드가 크고 자존심이 강하다는 것이다.
이것은 프로그래머가 가져야할 가장 기본적이며 필수적인 성격으로
계속 공부하여 발전하고 시대의 변화에 적응할 수 있는 힘을 준다.
이런 성격이 없으면 프로그래머로 살아남기 힘들고 도태되곤 한다.
내심 속으로 "내가 최고다" 라는 생각이 있어야 계속 살아남을 수 있는데
한국인들의 나쁜 습성중 하나와 만나면서 이로 인해 상처를 받기도 한다.
바로 "사촌이 땅을 사면 배가 아프다"는 것으로 남 잘되는 꼴을 못 본다는 것이다.
내가 최고로 잘 났는데 다른 사람이 실력이 좋아 보이거나 인정 받기 시작하면
그 모습을 보기 싫어 헐뜯고 약점을 공격하기 시작하는 것이다.
비슷한 성격의 사람들이 모인 집단인데 서로의 실력을 존중하고 칭찬해주는 문화가 생겼으면 좋겠다.
나는 항상 새로운 프로그래머를 만나면 그 사람의 실력을 칭찬해 주려고 노력한다.
(단, 신입 사원은 예외이다.)
이런 상황을 상처받지 않고 잘 넘기기 위한 지혜를 나는 공자의 논어에서 찾았는데
정말 큰 도움을 받은 구절이다.
人不知而不溫, 不亦君子乎(인부지지불온, 불역군자호)
남이 알아주지 않는다해도 화내지 않으니, 어찌 군자가 아니겠는가?
최근 덧글