티스토리 뷰

728x90
반응형

안녕하세요. Teus입니다.

 

이번 포스팅은 SIMD(Single Instrunction Multi Data)를 다룹니다.

 

C언어 Level의 Low수준을 다루게 됩니다.

1. SIMD란

SIMD는 Single Instrunction Multi Data의 약자 입니다.

 

글자로 알수 있듯, 하나의 명령어로 다수의 Data를 처리한는 방법 입니다.

 

아래 코드를 보실까요?

//일반계산
(int *)arr1 = (int *)malloc(sizeof(int)*8);
(int *)arr2 = (int *)malloc(sizeof(int)*8);
(int *)ret = (int *)malloc(sizeof(int)*8);
//8번의 정수연산을 위해서 8번의 반복문을 돌아야됨
for(int i=0; i<8; i++) {
    ret[i] = arr1[i] + arr2[i];
}

//simd계산
int* arr1_ori = (int*)_aligned_malloc(sizeof(int) * 8, 32);
int* arr2_ori = (int*)_aligned_malloc(sizeof(int) * 8, 32);
__m256i arr1 = _mm256_load_si256((__m256i*)arr1_ori);
__m256i arr2 = _mm256_load_si256((__m256i*)arr2_ori);
//하나의 명령어로 8개의 정수 덧셈을 처리할수 있음
__m256i ret = _mm256_add_epi32(arr1, arr2);

여기서 앵 이거 걍 함수로 한줄처리하면 되는거 아뇨?🤔 생각했으면 좀더 공부하셔야 합니다.

 

__mm256_add_epi32라고 하는 함수는 CPU의 명령어 수준에서 다수의 레지스터를 사용해서, CPU의 Cycle동안 더 많은 계산을 할 수 있게 준비되어있는 함수 입니다.

2. DataTypes

AVX/AVX2 명령어의 경우 아래처럼 DataType이 나뉘게 됩니다.

Data Type Description
__m128 128-bit vector containing 4 floats
__m128d 128-bit vector containing 2 doubles
__m128i 128-bit vector containing integers
__m256 256-bit vector containing 8 floats
__m256d 256-bit vector containing 4 doubles​
__m256i 256-bit vector containing integers
float = 32bit 실수
double = 64bit 실수
integer = 32bit or 64bit 정수

그래서 128 bit/32 bit per float => 4floats가 되는거죠

 

이때 128은 레지스터의 bit을 의미하며

 

레지스터의 bit이 증가할 수록 한번에 처리할 수 있는 Data의 양도 증가하게 됩니다.

(128bit SIMD는 한번에 128 bit의 데이터를 처리할 수 있는 것을 의미합니다)

3. DataFunc

이런 AVX DataType변수의 경우 단순한 연산을 하는것이 아니라

 

Cpu제조사에서 준비된 Intrinsic Instrunction을 사용하게 됩니다.

 

이 Nameing 체계를 보면 아래와 같습니다.
_mm<bit_width>_<name>_<data_type>

bit_widht : 128, 256, 512
name : 어떤연산을 할 것인지
data_type : 연산하는 Data의 type이 어느것인지

 

그래서 예를들어서

 

"256bit 레지스터를 가지고, 32bit정수의 더하기 연산을 하겠다" 하면

_mm256_add_epi32

 

같은 연산자를 사용하게 됩니다.

 

이때 뒤에 datatype의 경우 현재 epi32라고 나와있는데

 

이는 32bit signed Integer라고 가정하고 연산한다 를 의미합니다.

Data Type Description
ps vectors contain floats (ps stands for packed single-precision)
pd vectors contain doubles (pd stands for packed double-precision)
epi8/epi16/epi32/epi64 vectors contain 8-bit/16-bit/32-bit/64-bit signed integers
epu8/epu16/epu32/epu64 vectors contain 8-bit/16-bit/32-bit/64-bit unsigned integers
si128/si256 unspecified 128-bit vector or 256-bit vector
m128/m128i/m128d/m256/m256i/m256d identifies input vector types when they're different than the type of the returned vector

4. 간단한 예시

AVX는 기본적으로 <imminstrin.h>헤더파일을 통해서 함수를 제공합니다.

 

이제 두개의 Array를 만들고, 이 Array의 덧샘을 하는 간단한 코드를 보겠습니다.

#include <immintrin.h>
#include <stdio.h>

int main() {
  //float으로 구성된 data array생성
  __m256 arr1 = _mm256_set_ps(2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0);
  __m256 arr2 = _mm256_set_ps(1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0);

  //AVX 연산을 사용해서 data를 연산
  __m256 result = _mm256_add_ps(arr1, arr2);

  //pointer로 typecasting후 일반적인 포인터처럼 사용
  float* f = (float*)&result;  
  return 0;
}

3_1. DataFunc(scalar value로 초기화 하는 함수)

Function Description
_mm256_setzero_ps/pd Returns a floating-point vector filled with zeros
_mm256_setzero_si256 Returns an integer vector whose bytes are set to zero
_mm256_set1_ps/pd Fill a vector with a floating-point value
_mm256_set1_epi8/16/32/64 Fill a vector with an integer
_mm256_set_ps/pd Initialize a vector with eight floats (ps) or four doubles (pd)
_mm256_set_epi8/16/32/64 Initialize a vector with integers
_mm256_set_m128/128d/128i Initialize a 256-bit vector with two 128-bit vectors
_mm256_setr_ps/pd Initialize a vector with eight floats (ps) or four doubles (pd) in reverse order
_mm256_setr_epi8/16/32/64 Initialize a vector with integers in reverse order

위 함수들을 사용하면

직접 value값을 전달하고, 그 값을 통해서 새로운 simd용 datatype의 변수를 생성할 수 있습니다.

#include <stdio.h>
#include <stdlib.h>
#include <xmmintrin.h>
#include <immintrin.h>
int data_num = 1000000;
int thread_cnt = 8;

int main() {
    __m256d dbl_vector = _mm256_set1_pd(0.648);
    double* my_vec = (double*)&dbl_vector;
    for (int i = 0; i < 32; i++) {
        printf("idx : %d, value = %f\n", i, my_vec[i]);
    }
}

그러면 생성된 type을 통해서 일부 Array의 값이 초기화된것을 확인할 수가 있습니다.

3_2. DataFunc(Memory로 Data를 Load 하는 함수)

Data Type Description
_mm256_load_ps/pd Loads a floating-point vector from an aligned memory address
_mm256_load_si256 Loads an integer vector from an aligned memory address
_mm256_loadu_ps/pd Loads a floating-point vector from an unaligned memory address
_mm256_loadu_si256 Loads an integer vector from an
_mm_maskload_ps/pd, _mm256_maskload_ps/pd Load portions of a 128-bit/256-bit floating-point vector according to a mask
_mm_maskload_epi32/64, _mm256_maskload_epi32/64 Load portions of a 128-bit/256-bit integer vector according to a mask

위 경우는 aligned된 상태의 memory를 이용해 simd연산을 하는 함수들 입니다.

 

aligned_malloc이라는 함수를 사용하면 메모리는 일정 간격을 띄우면서 할당받을 수가 있습니다.

 

이때 aligned_malloc(sizeof(int)*8, 32) 라고 하면

 

아래와 같이 8개의 정수 크기만큼을 사용할 공간을 받으면서, Data는 32byte씩 떨어져 있는것을 보장받을 수가 있습니다.

image.png

다수의 Data를 빠르게 Load해야하는 SIMD연산에서 aligned 여부에 따라 성능이 차이나게 되는 것으로 알려져 있습니다.

 

그래서 기존에 할당받은 Memory가 aligned면 load를, aligned가 되어있지 않으면 loadu 함수를 사용합니다.

#include <stdio.h>
#include <stdlib.h>
#include <xmmintrin.h>
#include <immintrin.h>
int data_num = 1000000;
int thread_cnt = 8;

int main() {
    double data_cnt = 8;
    double* vec = (double*)_aligned_malloc(sizeof(double) * data_cnt, 32);    
    for (int i = 0; i < data_cnt; i++) {        
        vec[i] = 22;
    }

    __m256d simd_vec = _mm256_load_pd(vec);
    for (int i = 0; i < 32; i++) {
        printf("idx : %d, value = %f\n", i, vec[i]);
    }
    _aligned_free(vec);
}

위처럼 mem을 할당받고, 해당 mem은 typecasting혹은 위 simd 함수를 사용해서 simd를 위한 data type으로 만들수가 있습니다.

 

그 결과를 보시면 기존 memory값이 정삭적으로 출력되는것을 볼 수가 있습니다.

728x90
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함