SIMD

SIMD란

SIMD 란 Single Instruction, Multiple Data의 약자로 1번 명령수행으로 여러 데이터를 처리하는 방법이다. CUDA와 함께 프로세싱 고속화 방법으로 쓰이지만 개발기간이 CUDA보다 길다는 단점이 있다.

사용법

보통의 x86 CPU는 Visual Studio 를 설치하면 SSE, MMX, AVX를 쓸 수 있다. https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html 에서 명령어 레퍼런스를 검색을 할 수 있다.

  • SIMD는 기본적으로 4차원 벡터이다.
  • 3차원 벡터를 쓰는 경우 로드를 2번해야 할 수 있다.
  • 성능상 이점 : 3차원 벡터 < 4차원 벡터
  • SIMD 로드 방식이 4개 단위임.
  • SIMD는 로드에 오버헤드가 있음.
  • SIMD는 단일 연산 보다 느림.

기본적인 플로우는

  • 데이터 로드 -> 연산 -> 데이터 언로드

자료형

데이터 종류

  • __m64 : 64bit 레지스터. 64/8 로 8byte의 데이터를 처리 할 수 있다.
  • __m128 : 128bit 레지스터. 128/8 로 16byte의 데이터를 처리 할 수 있다.
  • __m256 : 256bit 레지스터. 256/8 로 32byte의 데이터를 처리 할 수 있다.
  • __m512 : 512bit 레지스터. 64/8 로 64byte의 데이터를 처리 할 수 있다.

접미사

256bit를 예로 든다.

  • __m256 : float type
  • __m256d : double type
  • __m256i : int type

명령어

명령어는 add, sub, or 등 의 연산을 명령어 레퍼런스에서 확인할 수 있다. 각 자료형에 따른 명령어가 다르기에 확인이 필요 하다.

로드 (검색 키워드 : load)

local 데이터를 레지스터에 올리는 작업. 오버헤드가 있다.

주의 할 점은 메모리에 있는 데이터를 레지스터에 넣을 때 원하는 타입으로 변환하는 문제를 신경 써야한다.

  
__m256i zero = _mm256_setzero_si256();
__m256i cmpsrc = _mm256_loadu_si256(__m256i*) & m_pSrc[offset]);
__m256i cmplo = _mm256_unpacklo_epi8cmpsrc, zero); // uchar -> short

__m256i cmphi = _mm256_unpackhi_epi8cmpsrc, zero);
__m256i cmplol = _mm256_unpacklo_epi16cmplo, zero); // short -> integer

__m256i cmploh = _mm256_unpackhi_epi16cmplo, zero);
__m256i cmphil = _mm256_unpacklo_epi16cmphi, zero);
__m256i cmphih = _mm256_unpackhi_epi16cmphi, zero);
__m256 pscmplol = _mm256_cvtepi32_pscmplol); // interger -> float

__m256 pscmploh = _mm256_cvtepi32_pscmploh);
__m256 pscmphil = _mm256_cvtepi32_pscmphil);
__m256 pscmphih = _mm256_cvtepi32_pscmphih);
  

위 코드는 이미지의 특정 포인터 데이터 32byte를 로드하여 실수 비교를 하기위한 데이터 변환의 예시이다. 연산 목적 데이터형은 float이고 4byte, 원본 데이터형은 uchar 1byte이다. 연속으로 되어있는 데이터를 4byte단위로 옮겨주는 작업을 한다.

연산 (검색 키워드 : add, sub, mul, div, or, and …)

데이터를 연산하는 과정. 일반 고수준 언어와는 작성법에는 차이가 있기에 개발 기간이 오래걸린다. 논리 연산을 이해해야 명령어를 이용한 로직 작성이 가능하다.

언로드 : (검색 키워드 : store)

연산한 레지스터 데이터를 메모리로 되돌리는 작업. 여기도 오버헤드가 있다.

  
// int -> short
__m256i rst1lo = _mm256_packus_epi3(rst1lol, rst1loh);
__m256i rst1hi = _mm256_packus_epi3(rst1hil, rst1hih);
// short -> char
__m256i rst1 = _mm256_packus_epi16(rst1lo,rst1hi);
_mm256_storeu_epi8(&m_pDst[offset], rst1);
  

위 코드는 로드때 분리하였던 데이터를 다시 합치는 코드이다. 4byte데이터를 2byte로, 2byte데이터를 1byte로 합친다.

고려할 점

SIMD를 작성 할 때 고려해야 할 사항은 로드,연산,언로드의 시간이 기존 연산보다 작을 때 고려 해야 한다. 대용량의 데이터를 처리 할 때 유용하며 OpenMP를 활용하면 병렬화도 가능하다.