OpenMP Parallel Programming (1)

OpenMP?

구성요소

  1. Compiler Directives
  2. Runtime routines
  3. Environment variables

structure block

#pragma omp //컴파일러 지시자
{
/*structure block*/
}

위에서 아래로 순차적으로 진행되며,
abort(), exit()을 통해서 빠져나올 수 있음

OMP 헤더파일

#include <omp.h>

컴파일 및 실행

cywgin64 터미널에서 실행한다.
설치 방법은 별도 링크에서 확인

gcc -o filename filename.c -fopenmp
./filename.exe

옵션 -fopenmp를 붙여주는 것이 중요하다.

Thread 관련 함수

omp_set_num_thread(n);

thread 개수 지정

omp_get_thread_num();

현재 쓰레드 번호 반환

omp_get_num_threads();

설정된 전체 쓰레드 개수 반환

Parallel

Parallel Region

#pragma omp parallel //컴파일러 지시자
{
/* code executed by each thread */
}

thread 개수만큼 fork 되고 각 thread가 코드를 동시에 수행하다가 join 하며 마무리된다.

Private Variables

pragma 밖에 선언된 변수는 모든 thread에서 공유될 수 있다.
이때 지시자에서 private으로 선언하면 각 thread로 공유(복사)되지 않는다.

#pragma omp parallel private(id)
{
    id = omp_get_thread_num();
    printf("My thread num = %d\n", id);
}

Master Region

오로지 하나의 thread만 실행해야 하는 코드가 있을 때 유용하다.

#pragma omp parallel
{
    #pragma omp master
    {
    int k = omp_get_thread_num();
    printf("My thread num = %d\n", k);
    }
}

Barrier

Thread 팀의 모든 Thread가 동기화 된다. 이 지점에서 다른 Thread가 종료되길 기다린다. 기다리고 싶지 않다면 nowait 지시문을 추가해준다.

Loop Parallel

index를 직접 지정해주는 pthread와 달리 openmp는 index를 알아서 핸들링해준다.

#pragma omp parallel for
    {
    for(int i=0; i<n; i++)
       c[i] = a[i];
    }

for문에서 인덱스를 선언해주거나 private 변수를 만들 필요가 없다.

Scheduling of Loop

앞서 언급한 것처럼 index를 알아서 핸들링해주지만, 각 thread가 어떤 값을 가지는지 정확히 하려면 스케쥴링이 필요하다.

#pragma omp parallel for schedule(mode, chunksize)

chunksize는 각 thread가 하는 최소한 작업양을 의미한다.

openmp에서는 5가지 스케줄링 모드를 지원한다.
1. static
2. dynamic
3. guided
4. auto
5. runtime

static

chunksize를 round-robin 방식으로 우선순위를 두지 않고, thread를 0부터 n까지 순차적으로 할당한다.
별도로 chunksize를 지정하지 않는다면 n 길이의 loop와 p개의 thread가 있을 때, 각 thread는 n/p 만큼의 chunksize 작업을 수행한다.

  • 코어에 한 프로그램만 돌아갈 때
  • 반복자(iteration)가 상수일 때
  • loop limit이 알려져 있을 때

사용하면 가장 좋은 방식이다.
serial하게 수행되는 것이 아니라 concurrent하게 수행된다.

#include <stdio.h>
#include <omp.h>
#define WORKSIZE 40
#define NTHREADS 4

int main(int argc, char *argv[])
{
   int data[NTHREADS][WORKSIZE];
   for(int i=0; i<WORKSIZE; i++){
      for(int j=0; j<NTHREADS; j++){
         data[j][i] = 0;
      }   
   }
   
   omp_set_num_threads(NTHREADS);
   
   #pragma omp parallel
   {
      #pragma omp for schedule(static, 4)
      for(int i=0; i<WORKSIZE; i++){
         data[omp_get_thread_num()][i] = i;
      }
   }
   
   for(int i=0; i<WORKSIZE; i++){
      printf("%d\t%d\t%d\t%d\n", data[0][i], data[1][i], data[2][i], data[3][i]); 
      
   return 0;
}

위 코드를 thread 4개로 돌렸다면,

thread0: 0, 1, 2, 3
thread1: 4, 5, 6, 7
thread2: 8, 9, 10, 11
thread3: 12, 13, 14, 15
thread0: 16, 17, 18, 19
...
와 같이 돌아갈 것이다.

만약 chunksize를 따로 지정해주지 않았다면 WORKSIZE 40을 THREAD 개수 4로 나눈 10개 단위로 THREAD마다 할당해 작업했을 것이다.

dynamic

chunk의 값에 따라 크기가 제어되며 사이즈는 고정된다. static이 모든 thread를 순차적으로 사용했다면, dynamic에서는 사용 가능한 thread에 동적으로 작업으로 할당한다.

guided

dynamic과 유사하지만, 작업의 크기가 계속 줄어드는 방식이다. minimum size를 지정할 수 있다.

WORKSIZE가 40이고 minimum size가 4라면, 10개 - 8개 - 8개 - 6개 - 4개 - 4개 처럼 점점 작업 크기가 작아질 것이다.

runtime

환경 변수에 모드를 지정해주는 방식이다. 이후 실행되는 파일은 환경 변수에 지정된 방식을 따른다.

$set env OMP_SCHEDULE [mode]

auto

위의 여러 방식 중 상황에 맞는 가장 적합한 방식을 알아서 정해서 실행해준다.

참고자료

https://yechan821.tistory.com/9

http://egloos.zum.com/himskim/v/3266925

http://egloos.zum.com/himskim/v/3273089

https://hpac.cs.umu.se/teaching/pp-18/

https://throwexception.tistory.com/660

좋은 웹페이지 즐겨찾기