본문 바로가기

카테고리 없음

OpenMP 소개: 공유 메모리 멀티 프로세서를 위한 포터블한 병렬 프로그램 API |

OpenMP 소개: 공유 메모리 멀티 프로세서를 위한 포터블한 병렬 프로그램 API | Solaris

2007.05.02 20:34

썬 스튜디오 컴파일러 (C/C++/포트란 95) 는 OpenMP 병렬화를 네이티브하게 지원합니다. OpenMP는 공유 메모리 모델에서 병렬 프로그램의 새로운 표준 모델로 발전하고 있습니다. 이 것은 컴파일러 지시자, 런타임 루틴, 그리고 프로그래머들이 코드를 쉽게 병렬화 하기 위한 환경 변수들을 제공하고 있습니다. 이 글에서는 OpenMP 에 대한 간략한 소개와 썬 스튜디오 컴파일러에서의 OpenMP 지원에 대해 설명합니다. 이 글은 OpenMP 와 포트란, C 혹은 C++ 에서 병렬프로그래밍을 처음 접하는 개발자들을 위해 쓰여 졌습니다.

OpenMP란 무엇인가?

OpenMP는 공유 메모리 환경에서 프로그램을 병렬화 하는 표준 입니다. OpenMP 는 컴파일러 지시자, 런타임 루틴 그리고 포트란, C, C++ 프로그램에서 공유 메모리 병렬화를 지정할때 프로그래머가 사용할 수 있는 환경 변수등을 제공합니다.

OpenMP 컴파일러 지시자는 프로그램내에서 사용되며 OpenMP를 인식하는 컴파일러가 멀티 쓰레드를 이용해서 병렬로 실행되는 실행파일을 생성하도록 지시 합니다. 소스 코드의 많은 수정이 필요 없습니다(최고의 퍼포먼스를 얻기 위해 튜닝을 할때를 빼고). OpenMP 컴파일러 지시자는 여러분이 다양한 아키텍쳐와 시스템 상에서 프로그램을 병렬화 하기 위한 우아하고, 일관적이고 포터블한 인터페이스를 사용 가능하도록 합니다. OpenMP는 광범위하게 채용되는 표준으로써 Sun, IBM, Intel, SGI 등이 지원하고 있습니다. (하단의 참고자료 링크를 이용해서 최신의 OpenMP 표준 문서를 확인하시기 바랍니다.)

OpenMP 는 쓰레드를 생성하고 관리해 줌으로써 병렬 프로그래밍을 좀더 발전된 레벨로 끌어 올려 줍니다. 여러분이 해야할일은 단순히 적절한 컴파일러 지시자를 소스 프로그램에 삽입시키고 OpenMP 를 지원하는 컴파일러를 이용해서 프로그램을 컴파일 하고 적절한 컴파일러 옵션을 사용 하는 것입니다 (썬 스튜디오는 -xopenmp 컴파일러 옵션을 사용). 컴파일러는 컴파일러 지시자를 해석하고 코드를 병렬화 시킵니다. OpenMP 를 지원하지 않는 컴파일러는 OpenMP 구문을 조용히 무시해 버립니다.

이 글은 OpenMP를 C 와 C++ 프로그램에서 사용하는 예제를 제공하지만 동일한 프로그램이 포트란95 에도 존재 합니다. OpenMP 유저 가이드에서 좀 더 상세한 정보를 알아보시기 바랍니다.

OpenMP 컴파일러 지시자

OpenMP 표준은 컴파일러 지시자의 셋을 정의 합니다. 지시자는 컴파일러에게 코드의 블럭을 어떻게 처리할 것인지를 알려 줍니다. 가장 많이 쓰이는 기본 지시자는 #pragma omp parallel병렬화 구역 임을 나타 냅니다.

OpenMP 는 병렬 실행시에 fork-join 모델을 사용 합니다. OpenMP 프로그램은 초기 쓰레드라고 하는 싱글 쓰레드로 시작 합니다. 쓰레드가 병렬 구조를 만나면 그 자신과 0개 혹은 그 이상의 추가 쓰레드로 구성된 쓰레드 팀을 만든 다음 그 자신이 새로운 팀의 마스터가 됩니다. 새로운 팀의 모든 멤버(마스터를 포함)는 병렬 구조상에서 코드를 실행 합니다. 병렬 구조의 끝에는 암묵적인 장벽이 존재 합니다. 오직 마스터 쓰레드만이 병렬 구문 이후의 유저 코드를 계속해서 실행할 수 있습니다.

병렬 지역을 실행하는 팀의 쓰레드 갯수는 몇가지 방법으로 조정할 수 있습니다. 한가지 방법은 환경 변수 OMP_NUM_THREADS 를 이용하는 것입니다. 또다른 방법은 런타임 루틴 omp_set_num_threads() 를 호출 하는 것입니다. 또 다른 방법은 num_threads 절을 parallel 컴파일러 지시자와 같이 사용하는 것입니다.

OpenMP 는 두가지 기본 두가지의 작업-공유 구조를 지원해서 병렬 지역 내의 작업이 팀 내의 쓰레드 간에 나뉘어 질 수 있도록 지정할 수 있습니다. 이러한 작업-공유 구조는 루프와 섹션 입니다. 루프를 위해 #pragma omp 이 사용 되고 #pragma omp sections섹션 을 위해 사용 됩니다 -- 병렬로 수행될 수 있는 코드의 블럭들.

#pragma omp barrier 는 팀의 모든 쓰레드가 장벽 이후의 코드를 계속 수행 하기 전에 다른 다른 쓰레드를 기다리도록 합니다. 이것은 병렬 지역의 끝에 암묵적인 장벽이라고 합니다. #pragma omp master 은 컴파일러에게 이후의 블럭은 오직 마스터 쓰레드에 의해서만 실행되도록 합니다. #pragma omp single 은 팀의 오직 하나의 쓰레드만이 해당 코드의 블럭을 실행시키도록 지시 합니다; 이 쓰레드는 꼭 마스터 쓰레드가 되어야 할 필요는 없습니다.여러분은 #pragma omp critical 지시자를 이용해서 오직 한번에 하나의 쓰레드만에 의해서 코드 블럭이 실행되도록 보호할 수 있습니다. 물론 이러한 지시자는 오직 parallel 지시자 안에서만 의미가 있습니다 (병렬 지역).

OpenMP 런타임 루틴

OpenMP 는 프로그램 상의 쓰레드에 대한 정보를 얻을 수 있는 몇가지 런타임 루틴을 제공 합니다. 것은 omp_get_num_threads(), omp_set_num_threads(), omp_get_max_threads(), omp_in_parallel(), 와 다른 루틴들을 포함합니다. 추가적 적으로 OpenMP 는 몇가지 lock 루틴을 제공해서 쓰레드 동기화에 사용할 수 있도록 해 줍니다.

OpenMP 환경 변수들

OpenMP 는 OpenMP 프로그램의 동작을 조정할 수 있는 몇가지 환경 변수들을 제공합니다.

가장 중요한 환경 변수는 OMP_NUM_THREADS 로 병렬 지역을 실행할때 사용되는 팀내의 쓰레드 갯수를 조정합니다 (팀의 마스터 쓰레드도 포함). 널리 사용되는 또 다른 환경 변수는 OMP_DYNAMIC 입니다. 이 환경 변수를 FALSE 로 설정함으로써 구현적으로 실행시에 동적인 쓰레드 갯수 조정을 비활성화 시킬 수 있습니다. 일반적인 규칙은 시스템상의 코어 보다 적은 숫자의 쓰레드 숫자를 지정하는 것입니다.

표준 OpenMP 환경 변수에 덧붙여서 썬 스튜디오 컴파일러는 썬-특수한 환경 변수의 셋을 추가 시켜서 런타임 환경을 좀 더 조정할 수 있도록 해 줍니다. 이러한 것들은 OpenMP 유저 가이드에 설명되어 있습니다.

예제

간단한 행렬 곱하기 프로그램을 이용해서 OpenMP가 어떻게 프로그램을 병렬화 시키는지 알아 봅시다. 다음의 코드 부분은 2개의 행렬을 곱하는 것입니다. 이것은 매우 간단한 예제로써 진짜로 훌륭한 행렬 곱셈 루틴을 원한다면 캐시 효과를 고려하거나 좀 더 낳은 알고리즘을 사용해야 합니다. (Strassen 혹은 Coppersmith 와 Winograd 의 알고리즘 등).

for (ii = 0; ii < nrows; ii++) { 
for (jj = 0; jj < ncols; jj++) {
for (kk = 0; kk < nrows; kk++) {
array[ii][jj] = array[ii][kk] * array[kk][jj];
}
}
}

위의 루프로 감싸진 각 레벨에서 각 루프 반복은 독립적으로 수행될 수 있습니다. 그러므로 위의 코드 세그먼트의 병렬화는 매우 직관적입니다: #pragma omp parallel for 지시자를 제일 외곽쪽의 루프 전에 (ii 루프) 추가시킵니다. 지시자를 제일 외곽쪽 루프에 삽입하는 것이 이득입니다. 왜냐하면 이것이 최고의 퍼포먼스 이득을 가져다 주기 때문입니다. 병렬화된 루프에서 변수 array, ncolsnrows 는 쓰레드간에 공유 되고 ii, jj, 와 kk 는 각 쓰레드에 독립적입니다. 이제 이전의 코드는 다음과 같이 변경됩니다:

#pragma omp parallel for shared(array, ncols, nrows) private(ii, jj, kk) 
for (ii = 0; ii < nrows; ii++) {
for (jj = 0; jj < ncols; jj++) {
for (kk = 0; kk < nrows; kk++) {
array[ii][jj] = array[ii][kk] * array[kk][jj];
}
}
}

또다른 예제로 0 <= x < n. 구간에서 f(x) 의 합을 구하는 다음의 코드 부분을 보시기 바랍니다.

for (ii = 0; ii < n; ii++) {
sum = sum + some_complex_long_fuction(a[ii]);
}

위의 코드를 병렬화 하기 위해서 첫번째 단계는 다음과 같습니다.

#pragma omp parallel for shared(sum, a, n) private(ii, value)
for (ii = 0; ii < n; ii++) {
value = some_complex_long_fuction(a[ii]);

#pragma omp critical
sum = sum + value;
}

혹은 좀더 낳은 방법으로 reduction 절을 이용할 수 있습니다

#pragma omp parallel for shared(a, n) private(ii) reduction(+: sum) 
for (ii = 0; ii < n; ii++) {
sum = sum + some_complex_long_fuction(a[ii]);
}

시작하기

프로그램을 병렬화 시키는 방법은 여러가지가 있습니다. 첫째로 여러분이 병렬화가 필요한지 결정합니다. 몇몇 알고리즘은 병렬화에 적당하지 못합니다. 만약 새로운 프로젝트를 시작한다면 여러분은 병렬화가 가능한 알고리즘을 선택할 수 있습니다. 코드가 올바른지 (직렬환경에서) 확인하는 것이 병렬화를 시도 하기 전에 가장 중요 합니다. 직렬화된 실행시간을 반드시 측정해서 병렬화가 유용한지에 대해서도 판단하시기 바랍니다.

직렬로 실행되는 버전을 최적화 옵션을 이용해서 컴파일합니다. 컴파일러는 보통 여러분이 할 수 있는 것 보다 더 많은 최적화작업을 수행할 수 있습니다.

프로그램을 병렬화할 준비가 되었다면 썬 스튜디오의 수 많은 기능과 툴들이 여러분의 목적을 이루도록 도와줄 것입니다. 간단하게 이러한 기능들에 대해 설명합니다.

자동 병렬화

컴파일러의 자동 병렬화 옵션을 사용해 봅니다 (-xautopar). 병렬화를 컴파일러에게 넘김으로써 여러분이 노력할 필요 없이 컴파일러가 알아서 프로그램을 병렬화 해줍니다. 자동병렬화는 또한 OpenMP 지시자를 이용해서 병렬화 할수 있는 코드의 부분들을 인식할 수 있도록 도와주고 반대로 병렬화를 하지말아야 할 코드의 부분(예를 들어 내부 루프 의존 같은)도 알려 줍니다. 여러분은 컴파일러가 알려주는 정보를 -g 플래그를 이용해서 프로그램을 컴파일 해서 썬 스튜디오의 er_src(1) 를 이용함으로써 확인할 수 있습니다.


% cc -g -xautopar -c source.c
% er_src source.o

자동범위지정

OpenMP 프로그램시에 일반적인 타입의 오류는 범위 지정의 오류 입니다. 즉 변수의 범위가 잘못 지정되어서, 예를 들어 private(shared) 로 지정되어야 할 변수가 shared (private) 로 지정되는 것 입니다. 자동범위 지정 기능은 썬 스튜디오 컴파일러가 변수의 범위를 자동으로 결정해 줍니다. 두가지 OpenMP 의 확장 기능이 지원됩니다: __auto 절과 default(__auto) 절. OpenMP 의 유저 가이드에서 자세한 내용을 확인하시기 바랍니다.

dbx 디버거

썬 스튜디오 디버거 dbx 는 쓰레드 디버깅이 가능한 디버거로 여러분의 OpenMP 프로그램을 디버깅하는데 도움을 줍니다. OpenMP 프로그램을 디버깅 하려면 첫째로 -xopenmp=noopt -g 컴파일러 옵션을 이용해서 어떠한 최적화도 적용하지 않은채 컴파일하고 dbx 를 결과 실행파일을 이용해서 실행합니다. dbx 를 이용해서 여러분은 병렬 지역에 브레이크 포인트를 설정할 수 있고 병렬 지역을 순차적으로 실행시킬 수 있고 쓰레드에 private 한 변수를 검사 할 수 있고 기타 여러가지 작업을 할 수 있습니다.

퍼포먼스 분석기

썬 스튜디오 퍼포먼스 분석기를 통해 프로그램의 병목현상을 잡아낼 수 있습니다. 이 툴은 사용자가 대부분의 시간이 소요되는 핫 루틴을 프로그램상에서 잡아낼 수 있습니다. 또한 작업 및 대기 시간, 함수의 속성, 소스 라인, 그리고 명령어등 OpenMP 프로그램의 병목현상을 잡아 낼 수 있도록 도와 줍니다.

OpenMP 와 MPI 혼용하기

MPI (Message Passing Interface) 는 병렬 프로그램의 또 다른 모델입니다. OpenMP 와는 다르게 MPI 는 여러개의 프로세스를 실행시키고 TCP/IP 를 통해 통신 합니다. 이러한 프로세스들은 동일한 주소 공간을 공유하지 않기 때문에 원격 머신에서 실행 될 수 있습니다(혹은 클러스 환경). OpenMP 와 MPI 중 어느것이 더 낳다고 말하기는 어렵습니다. 두가지 모두 장단점이 존재 합니다. 더 흥미로운 점은 OpenMP 가 MPI 와 함께 사용될 수 있다는 것입니다. 일반적으로 MPI 를 사용해서 작업을 여러 머신으로 정련되지 않은 채로 분산시킨 다음 OpenMP 를 이용해서 각 싱글 머신의 병렬화 레벨을 좀더 향상 시키는 방법을 사용 합니다.

요약하자면 썬 스튜디오 컴파일러와 툴은 OpenMP 를 네이티브하게 지원하고프로그램을 병렬화 할 수 있는 수 많은 유용한 기능을 제공 합니다. 자세한 정보는 아래의 참고자료 섹션을 참고 바랍니다.

Resources