본문 바로가기

Programming/OpenMP

OpenMP (반복루프, 작업, 태스크)의 병렬처리 방법

  • 반복 루프의 병렬처리

 

반복 루프 작업을 여러 개의 스레드로 나누어 빠르게 처리할 수 있는 방법을 알아본다.

 

OpenMP 지시어는 다음과 같은 의미로 쓰이게 된다.
1. #pragma omp parallel : 지정된 스레드에 맞춰 스레드를 생성해 주세요.2. #pragma omp for : 다음에 나오는 for 루프 작업을 생선된 스레드에 맞춰서 배분해 주세요.

 

// for 루프 작업 분할이 된 병렬프로그램
// forparallel.c

#include "stdafx.h"
#include <omp.h>
#include "math.h"

int _tmain(int argc, _TCHAR* argv[])
{
	const int MAX = 100000000;
	float* Data;
	Data = new float[MAX];

	int i = 0;

	for (i = 0; i < MAX; i++)

	Data[i] = i;



#pragma omp parallel

	{

#pragma omp for
	for (i = 0; i < MAX; i++)
	Data[i] = sqrt(Data[i]);
	}


	printf("Data : %f, %f, %f, %f, %f\n", Data[0], Data[1], Data[2], Data[3], Data[4]);
 	delete Data;
    
    return 0;
}

 

  • 작업의 병렬처리

 

작업 단위로 병렬화하는 것을 살펴보자

작업의 병렬 처리는 각기 다른 작업을 여러 개의 스레드가 작업의 종류별로 처리하는 것을 의미

작업 A : 입출력이 많으며 계산이 적음

작업 B : 입출력이 적고 계산이 많음

 

2개의 스레드가 서로 다른 작업을 배분받아 수행하게 된다. 해야 할 작업이 스레드 별로 다르기 때문에 어떤 스레드가 먼저 종료될지는 알 수 없어 하나의 스레드가 미리 끝났을 경우 다른스레드의 작업시간 동안 대기하여 작업시간이 동일하게 된다.

 

#include "stdafx.h"
#include <omp.h>
#include "Math.h"

int tmain(int argc, _TCHAR* argv[]) {

	const int MAX = 100000000;
	int i = 0;

	float* Data1 = new float[MAX];
	float* Data2 = new float[MAX];

	InputData(Data1, MAX);
	InputData(Data2, MAX);

#pragma omp parallel
{
#pragma omp sections
	{
#pragma omp section
		CalcSQRT(Data1, MAX);
#pragma omp section
			CalcLOG(Data2, MAX);
	}
}
	printData(Data1);
	printData(Data2);

	delete Data1;
	delete Data2;
    
    return 0;
}

 


 

  • 태스크(Task) 병렬처리

 

앞서 살펴본 루프 병렬화와 작업 분할 병렬화의 주체는 스레드이다.

루프 병렬화는 스레드가 반복 루프 작업을 배분받아 실행한다. 

작업 분할 병렬화는 스레드가 서로 다른 작업을 배분받아 실행한다.

병렬화 시점부터 스레드가 작업을 받아서 수행하는 것이다.

 

지금부터 살펴볼 태스크 병렬처리는 병렬화의 주체가 태스크이다.

태스크 병렬처리 방법은 마치 회사에서 오늘까지 해야할 일감이 쌓여있고, 그 일을 다 마치면 퇴근하는 것과 같다.

 

1. 병렬처리를 수행해야 할 작업 전부를 한 번의 함수 호출로 진행되는 작업 또는 한 번의 구문으로 실행할 수 있는 작업으로 태스크의 단위를 작게 나눈다.

하나의 작업 = 1 / n(태스크로 나눈 총 개수)

2. 태스크 단위로 분리된 n개의 작업을 하나의 스레드가 태스크 큐에 모아 놓는다.

3. OpenMP 지시어로 생성된 스레드 중에서 일할 준비가 된 스레드부터 태스크를 가져와 실행한다.

4. 스레드가 하나의 태스크를 완료하면 다시 유휴 상태가 되어 일할 준비가 된다. 다시 작업 큐에 있는 태스크를 가져와 실행한다. 모든 태스크가 완료될 때까지 계속 반복한다.

 

 

이렇게 작업의 주체가 태스크가 되면서 OpenMP에서 지원하는 병렬화의 범위가 넓어졌다. 태스크 병렬화를 적용하면 재귀함수의 호출, C++반복자(iterator)를 이용한 구문, while문을 이용한 루프 구문에서 더 효율적인 프로그래밍을 할 수 있다.

 

 

Task 지시어 사용 방법

#pragma omp parallel // 멀티 스레드가 생성됨
{
#pragma omp single // for 루프 회수만큼 일감을 쌓으세요
	for(i = 0; i < MAX; i++)
	{
		#pragma omp task //하나의 일감을 구분하는 단위
		{
			//태스크 처리하는 코드
		}
	}
}

 

병렬영역 시작 부분에서 #pragma omp parallel 지시어가 사용되며 병렬 스레드가 생성된다. 다수의 스레드가 생성되지만 #pragma omp single 지시어를 사용하여 하나의 스레드만 동작하게 된다.

 

하나의 스레드가 태스크 큐에 태스크 를 만들어 넣는 작업을 하게 된다. 하나의 스레드가 해야 할 작업을 for 루프 횟수만큼 반복하면서 작업장에 쌓아놓게 된다. 한개의 작업의 크기는 #pragma omp task로 둘러싸인 만큼의 양이다.

 

#pragma omp task 지시어의 영역으로 지정된 작업을 하나의 태스크로 설정하여 max카운트에 해당하는 개수만큼 태스크 큐에 들어간다. 태스크 큐에 작업이 들어가면 유휴 스레드부터 태스크를 가져와 작업하게 된다.

 

병렬영역 내에서 실행되는 재귀함수는 루프 병렬화로 효율을 내기가 어려운 부분이 있다. 재귀함수의 대표적인 예로 많이 나오는 피보나치 수열을 통해 알아보자.

 

// Fibonacci.cpp


#include "stdafx.h"
#include "omp.h"
int Fibonacci(int n){ 
	int x, y;
	if (n < 2) return n; 
	else { 
    	x = Fibonacci(n - 1);
		y = Fibonacci(n - 2);
		return (x + y); 
    }
}

int FibonacciTask(int n){ 

	int x, y; 
	if (n < 2) return n; 
	else {
		#pragma omp task shared(x) x = Fibonacci(n - 1);
		#pragma omp task shared(y) y = Fibonacci(n - 2);
		#pragma omp taskwait return (x + y); 
    }
}

int _tmain(int argc, _TCHAR* argv[]){ 

	const int MAX = 41;
	int FibNumber[MAX] = { 0 };
 	int i = 0;
#pragma omp parallel {

#pragma omp single private(i);
 	for (i = 1; i < MAX; i++) { 
    	FibNumber[i] = FibonacciTask(i); 
    	} 
	}
 	printf("피보나치수 : "); 
 	for (i = 1; i < MAX; i++) 
 		printf("%d ", FibNumber[i]);
    return 0;
}

 

 

 

 

 

반응형
LIST