본문 바로가기
Programming/C | C++

OpenMP(1) - 입문자용 주요 사용법 간단한 설명/정리(omp parallel, for, atomic,critical)

by 썽하 2020. 7. 24.

 

 

매일 GPU나 분산 컴퓨팅으로 병렬 처리만 하다가, 오랜만에 단일 cpu환경에서 thread로 병렬 처리를 해야 할 일이 생겼다. 학습하는 김에 주요 사용법을 정리해보고자 한다.

 

개발 환경

  • C/C++
  • OpenMP 5.0 API

 

사용법

1. omp parallel

기본 사용법

#pragma omp parallel
//병렬 처리 할 한줄

#pragma omp parallel
{
	/*
	병렬 처리 할 여러줄
	*/
}

다음 한 줄이나 다음에 오는 중괄호에 묶인 구문을 병렬로 실행한다. thread 수만큼 실행된다.

 

 

#pragma omp parallel
printf("thread num %d : first hello world\n", omp_get_thread_num()); // thread 개수만큼 실행
printf("thread num %d : second hello world\n", omp_get_thread_num()); // 한번만 실행
return 0;
/*
thread num 0 : first hello world
thread num 2 : first hello world
thread num 1 : first hello world
thread num 3 : first hello world
thread num 0 : second hello world
*/
#pragma omp parallel
{
printf("thread num %d : first hello world\n", omp_get_thread_num()); // thread 개수만큼 실행
printf("thread num %d : second hello world\n", omp_get_thread_num()); // thread 개수만큼 실행
}
/*
thread num 0 : first hello world
thread num 0 : second hello world
thread num 3 : first hello world
thread num 3 : second hello world
thread num 1 : first hello world
thread num 1 : second hello world
thread num 2 : first hello world
thread num 2 : second hello world
*/

 

옵션

#pragma omp parallel if(condition)
#pragma omp parallel num_threads(n)
#pragma omp parallel private(a)
#pragma omp parallel firstprivate(b)
#pragma omp parallel shared(c)

중복되는 옵션은 한 번만 설명합니다.

 

if(condition) : 조건이 참일 때만 병렬 처리된다.

 

bool is_parallel = false;
#pragma omp parallel if(is_parallel) // if 문이 false 이므로 병렬처리 되지 않는다.
{
printf("thread num %d : first hello world\n", omp_get_thread_num());
printf("thread num %d : second hello world\n", omp_get_thread_num());
}

 

num_threads(n) : n개의 thread로만 병렬 처리한다.

 

int n = 2;
#pragma omp parallel num_threads(n)
{
printf("thread %d\n", omp_get_thread_num()); // n개의 thread가 각각 1번씩 실행
}
/*
thread 0
thread 1
*/

 

private(a) : 변수 a를 각각 copy 해서 사용한다. parallel region 이전의 초기화가 무의미하다.

firstprivate(b) : 변수 b를 각각 copy 해서 사용한다. parallel region 이전 값이 유지된다.

 

int a = 1234;
#pragma omp parallel private(a) num_threads(2)
{
printf("private 1 : %d\n", a); // 초기화 되지 않는다. dummy 출력
}
#pragma omp parallel private(a) num_threads(2)
{
a = 123;
printf("private 2 : %d\n", a); // 내부 초기화. 정상값 출력
}
#pragma omp parallel firstprivate(a) num_threads(2)
{
printf("private 3 : %d\n", a); // 외부 초기화. 정상값 출력
}
/*
private 1 : 0
private 1 : 32510
private 2 : 123
private 2 : 123
private 3 : 1234
private 3 : 1234
*/

 

shared(c) : 변수 c를 모든 thread가 하나의 copy로 사용한다. parallel region 이전의 값이 유지된다.

 

int c = 1234;
#pragma omp parallel shared(c)
{
printf("thread %d : shared c %d\n", omp_get_thread_num(), c++); // thread 끼리 변수 공유 O
}
c = 1234;
#pragma omp parallel firstprivate(c)
{
printf("thread %d : private c %d\n", omp_get_thread_num(), c++); // thread 끼리 변수 공유X
}
/*
thread 0 : shared c 1236
thread 3 : shared c 1237
thread 2 : shared c 1235
thread 1 : shared c 1234
thread 0 : private c 1234
thread 2 : private c 1234
thread 1 : private c 1234
thread 3 : private c 1234
*/

 

주의! shared 예제는 자원 동시 접근이 가능해서 따라 하면 안됨. 아래 atomic이나 critical을 참고.

 

2. omp for

#pragma omp parallel
#pragma omp for
// for-loop

// or 

#pragma omp parallel
{
	#pragma omp for
	// for-loop
}

for 혼자서는 동작하지 않고 parallel 구문 안에 있어야 정상 동작한다.

 

#pragma omp parallel
#pragma omp for
for(int i = 0 ;i < 5; i++)
{
printf("thread %d : %d (in parallel)\n", omp_get_thread_num(), i);
}
#pragma omp for
for(int i = 0 ;i < 5; i++)
{
printf("thread %d : %d (not in parallel)\n", omp_get_thread_num(), i);
}
/*
thread 0 : 0 (in parallel)
thread 1 : 1 (in parallel)
thread 2 : 2 (in parallel)
thread 3 : 3 (in parallel)
thread 4 : 4 (in parallel)
thread 0 : 0 (not in parallel)
thread 0 : 1 (not in parallel)
thread 0 : 2 (not in parallel)
thread 0 : 3 (not in parallel)
thread 0 : 4 (not in parallel)
*/

3. omp parallel for

#pragram omp parallel for
// for-loop

#pragma omp parallel
#pragma omp for

와 동일하다. 그럼 왜 두 줄로 나눠쓰자? 아마 parallel에만 들어갈 수 있는 옵션 때문에?

4. omp atomic

#pragma omp atomic
// atomic section

연산을 atomic 하게 한다. 공유 변수에 접근할 때 쓴다.

지원되는 연산 종류만 가능하며, 불가능한 경우 critical section으로 대체 가능하다.

지원되는 연산

 

int sum;
sum = 0;
#pragma omp parallel for
for(int i = 0 ;i < 100; i++)
#pragma omp atomic
sum += i;
printf("atomic sum : %d\n", sum);
sum = 0;
#pragma omp parallel for
for(int i = 0 ;i < 100; i++)
sum += i;
printf("not atomic sum : %d\n", sum);
/*
atomic sum : 4950
not atomic sum : 2071
*/
view raw omp_atomic.cpp hosted with ❤ by GitHub

 

5. omp critical

#pragma omp critical
// critical section

충동할 수 있는 자원에 대해서 critical section으로 설정한다. atomic보다 더 오래 걸리니 atomic이 불가능할 때 사용하자.

 

std::vector<int> v;
#pragma omp parallel num_threads(4)
{
if(omp_get_thread_num() == 0)
sleep(1);
v.push_back(omp_get_thread_num());
}
for(std::vector<int>::iterator it = v.begin() ; it != v.end() ; it++)
printf("%d\n", *it);
// 세그멘테이션 오류 (core dumped)
std::vector<int> v;
#pragma omp parallel num_threads(4)
{
if(omp_get_thread_num() == 0)
sleep(1);
#pragma omp critical
v.push_back(omp_get_thread_num());
}
for(std::vector<int>::iterator it = v.begin() ; it != v.end() ; it++)
printf("%d\n", *it);
/*
1
3
2
0
*/

 

 

Reference

 

쓰다 보니 많아져서 다음 편에 이어서...

댓글