윈도우 환경에서 OpenMP를 사용하기 위해서는 Visual Studio 8.0(Visual Studio 2005)버젼 이상, 또는 인텔 컴파일러 8.0버젼 이상의 컴파일러가 있으면 된다. Visual Studio 8.0 버젼 이상에서는 기본적으로 OpenMP를 지원하기 때문에 프로그램 설치 없이 설정만으로 OpenMP의 지원을 받을 수 있다. OpenMP를 사용하기 위해서는
[프로젝트 속성] => [구성 속성] => [C/C++] => [언어] => [OpenMP 지원] 을 '아니오' 에서 '예'로 변경한다.
OpenMP 라이브러리를 사용하기 위해 <omp.h>를 포함한다.
Visual Studio 9.0에서는 OpenMP 2.0까지만 지원한다. 2008년 발표된 OpenMP 3.0 이상의 기능을 사용하기 위해서는 인텔 컴파일러 11.x 이상의 버젼을 사용해야 한다.
#pragma omp for 지시어를 추가하게 되면 생성된 스레드가 for 루프 작업을 스레드 개수에 맞추어 자동으로 분배해 준다.
예 ) 2개의 코어를 가진 CPU인 경우
0번 스레드 : for ( i = 0 ; i < 50000000 ; ++i )
1번 스레드 : for( i = 50000000 ; i < 100000000 ; ++i )
#pragma omp parallel 지시어를 만나게 되면 동작하던 마스터 스레드는 자신을 포함하여 설정된 개수에 맞게 복수의 스레드를 생성하게 된다.
#pramga omp parallel 지서어 뒤에 num_threads( 숫자 ) 보조 지시어를 추가하여 스레드를 지정한 숫자만큼 요구할 수 있다.
#pragma omp sections 지시어는 생성된 멀티스레드가 작업 분할로 수행해야 할 영역을 나타내게 된다. 중괄호( {} )로 지정된 영역은 작업 분할 영역이 된다. 작업 분할 영역 안에 있는 코드들은 #pragma section 지시어로 작업 단위를 지정해야 한다.
OpenMP 버젼 3.0 이상부터 #pragma omp task 지시어와 #pragma omp taskwait 보조 지시어가 추가되어 태스크에 대한 병렬화를 지원한다.
태스크 병렬처리는 병렬화의 주체가 태스크이다. 해야 할 일감의 단위가 주체가 되는 것이다. 태스크 병렬처리 방법은 마치 회사에서 오늘까지 해야 할 일감이 쌓여 있고, 그 일을 마치면 퇴근하는 것과 같다.
위 예는 task 지시어를 사용하여 병렬화하는 구조를 나타냈다. 병렬 영역 시작 부분에서 #pragma omp parallel 지시어가 사용되며 병렬 스레드가 생성된다. 다수의 스레드가 생성되지만 #pragma omp single 지시어를 사용하여 하나의 스레드만 동작하게 된다.
하나의 스레드가 태스크 큐에 태스크(일감)를 만들어 넣는 작업을 하게 된다. 하나의 스레드가 해야 할 작업을 for 루프 횟수만큼 반복하면서 작업장에 쌓아놓게 된다. 한 개의 작업의 크기는 #pragma omp task로 둘러싸인 만큼의 양이다.
#pragma omp task 지시어의 영역으로 지정된 작업을 하나의 태스크로 설정하여 MAX 카운트에 해당하는 개수만큼 태스크 큐에 들어간다. 태스크 큐에 작업이 들어가면 유휴 스레드(작업을 할 준비가 되어 있는 스레드)부터 태스크를 가져와 작업하게 된다.
위 예는 피보나치 함수가 한번 호출되는 작업을 하나의 태스크로 묶어서 분할하였다. 이렇게 태스크를 구성하면 각각의 태스크의 크기가 동일하게 된다.
Visual Studio 8.0 이상에서 사용 가능 ( 주의 : 한글 폴더를 사용하면 오류가 발생할 수 있음 )
Intel Parallel Studio를 사용하여 다양한 기능을 사용하기 위해서는 해당 [Visual Studio 프로젝트]를 [인텔 프로젝트]로 변환한다. [인텔 프로젝트]로 변환하면 디폴트 컴파일러가 Visual Studio 컴파일러에서 인텔 컴파일러로 바뀌게 된다.
[메뉴바] -> [프로젝트] -> [Intel Parallel Composer] -> [Use Intel C++]
[솔루션 탐색기] -> [프로젝트 마우스 우클릭] -> [Intel Parallel Composer] -> [Use Intel C++]
프로젝트를 변환한 뒤에는 컴파일러가 변경되었으므로 솔루션을 다시 빌드해야 한다.
[프로젝트] -> [해당 프로젝트 속성] -> [프로젝트 구성 속성] -> [C/C++] -> [Debug] -> [Enable Parallel Debug Checks] 항목을 'Yes/debug:parallel'로 설정한다.
[메뉴바] -> [디버그] -> [예외]를 선택하고 [Win32Exceptions]에서 아래의 항목을 체크한다.
[메뉴바] -> [디버거] -> [Intel Parallel Composer] -> [Windows] -> [OpenMP]항목을 선택하면 다음과 같은 6개의 객체를 선택할 수 있다.
[Tasks], [Spawn Tree], [Locks], [Barriers], [Teams], [Taskwaits]
Tasks 윈도우는 현재 프로그램에서 동작하는 OpenMP의 태스크가 표시된다. 태스크를 진행하는 스레드 팀과 태스크의 속성, 상태를 알 수 있다.
ID 태스크의 고유 번호
State [Suspended] 라면 대기 중인 것을, [Running] 이라면 실행 중인 것을 나타낸다.
Type [Implicit] 암시적으로 OpenMP가 부여한 속성
[Explicit] 명시적 사용자가 #pragma omp task 지시어나 _task에 의해서 속성을 부여한 태스크
[tied] 태스크 작업 중간에 스레드가 대기 상태에 들어갔다가 다시 진행하게 될 때 같은 스레드가 계속 태스크를 진행해야 한다.
[untied] 태스크 작업 중간에 스레드가 대기 상태에 들어갔을 때 달느 스레드가 이어받아 진행할 수 있다. 해당 태스크가 아직 시작하지 않아 할당받은 스레드가 없을 때도 untied로 표시된다.
Team 태스크를 진행하는 스레드 팀 ID
Parent 태스크를 생성한 부모 태스크 ID
#Spawned 분기된 태스크의 개수
Thread 태스크를 진행하는 스레드 ID
Created At 그 태스크가 생성된 프로그램과 코드의 행을 나타낸다.
Tasks 윈도우와 동일하다. 최초로 생성된 태스크를 기점으로 트리 형태로 태스크를 나타낸다.
생성된 배리어 객체의 상태를 알려준다.
ID 각 배리어의 고유번호
State 배리어의 현재 상태
ex) State가 'Wait For Threads'이면 배리어까지 모든 스레드가 도달하기를 기다리고 있는 것이다.
Type 배리어의 종류
Team 배리어가 기다리는 스레드 팀의 아이디
#Thread Reached 배리어에 도달한 스레드 개수
#Wait For Tasks 기다리는 태스크의 개수
Created At 배리어가 생성된 코드의 위치
프로그램에서 실행 중인 스레드 팀의 정보가 표시된다.
ID 스레드 팀의 고유번호
Parent 스레드 팀을 생성한 부모 팀의 ID
Threads 스레드 팀이 가지는 스레드의 개수
Created At 스레드 팀이 생성된 코드의 위치
현재 실행되고 있는 록 객체의 상태를 표시한다.
ID 록의 고유 번호
State 록의 상태
Free 록을 소유한 스레드가 없는 상태로 록을 취득할 수 있다.
Held 록을 소유한 스레드가 있으며 다른 스레드는 록을 취득할 수 없다.
Type 록의 종류
simple 일반적인 록 객체로 중복해서 록을 설정할 수 없다. 사용 후 반드시 록을 해제해야 한다.
Nested 내포 록 객체로 1회 이상 록 설정을 할 수 있다. 록을 중복해서 설정하면 록 카운트가 증가하고, 사용 후에는 같은 횟수로 록 해제를 해야 한다.
Times Locked 록이 사용된 횟수
#Waiting Tasks/Threads 록이 풀리기를 기다리는 태스크/스레드 수
Initialzed At 록이 초기화된 코드의 위치
Taskwait의 발생 정보를 나타낸다.
ID Taskwait의 고유번호
State Taskwait의 상태
#Wait For Tasks 기다리는 태스크의 수
Thread Encountered Taskwait 와 만난 스레드 ID
Created At Taskwait 선언된 코드의 위치
병렬 프로그램에서 발생하는 버그를 탐지하거나 디버그를 하기 위한 편의 기능을 제공한다.
Parallel Debugger는 공유 메모리 변수를 여러 스레드가 접근하여 읽기와 쓰기를 하려고 할 때 이벤트를 발생시켜 자동으로 break 상태로 들어간다. 사용자는 해당 변수에 여러 스레드가 동시에 접근하여도 문제가 없는지를 판단할 수 있다.
[메뉴바] -> [디버그] -> [Intel Parallel Debugger Extension] -> [Thhread Data Sharing Detection] -> [Enable] 설정을 한다. Thread Data Sharing Detection 기능을 활성화 시키면 Stop on Event 기능이 동작하게 된다. 이것은 경쟁 상태가 발생하면 break로 들어가 사용자가 공유 변수를 살필 수 있도록 도와준다.
문제가 없는 코드라고 판단되면, 디버그 모드로 계속 실행할 때 같은 지점에서 동일한 이벤트가 발생하여 break 되는 것을 방지하도록 설정할 수 있다. 이벤트가 발생한 변수 또는 객체에 대해서 같은 이벤트가 발생할 경우 필터링을 설정하는 것이다.
Thread Data Sharing Events 윈도우에서 무시하고자 하는 객체를 선택하여 마우스 오른쪽 버튼을 클릭한다.
[Suppress Reporting of Future Accesses] -> [To this Data Object]
객체를 지정하여 필터링하는 방법과 함께 다른 4가지 옵션을 선택할 수도 있다.
From this Access 이 액세스만을 제외한다.
From this Function 이 함수로부터의 액세스를 제외한다.
From this Source File 이 원시 파일 중에서의 액세스를 제외한다.
From this Source Line 이 소스 라인으로부터의 액세스를 제외한다.
하나의 함수를 여러 스레드가 동시에 호출하여 사용하면 공유 변수 또는 정적 변수에 대한 경쟁 상태가 발생할 가능성이 크다. 하나의 스레드가 사용 중인 함수를 다른 스레드가 동시에 진입하여 사용하게 되면 재진입 이벤트가 발생하게 된다. 이벤트가 발생하면 Parallel Debugger는 자동으로 디버그 모드를 중단하고 사용자에게 알려준다. 함수의 재진입 탐지에 대한 설정은 다음과 같다.
[메뉴바] -> [디버그] -> [Intel Parallel Debugger] -> [Break on Reentrant Call]
병렬 영역의 구조 변경이나 스레드를 1개로 설정하는 코드를 추가하지 않아도 병렬 영역을 순차 실행할 수 있도록 도와준다. 이 기능은 병렬 프로그램의 버그 발생 시에 간단한 초기 디버그 시행에 많은 도움을 준다.
[메뉴바] -> [디버그] -> [Intel Parallel Debugger] ->[Activate / Deactivate Serialize Parallel Regions]
SSE 레지스터 디버그 윈도우는 SIMD 병렬처리를 수행할 때 XMM 전용 레지스터에 있는 값의 변화를 알 수 있도록 도와준다. SIMD는 명령어 수준의 병렬처리 방식이다. 현재 대부분의 CPU에서 SIMD를 지원하고 있으므로 데스크톱 PC에서는 바로 적용할 수 있다. 자세한 내용은 "MMX, SSE를 이용한 C, C++ 병렬 프로그래밍, 정영훈(프리렉)"을 참조.
병렬 프로그램을 개발하면 아래의 두 가지 에러가 가장 많이 발생하게 된다.
Memory Error
Threading Error
프로그램에서 잘못된 메모리 사용에 대한 에러와 예외를 탐지하는 기능을 제공한다.
[메뉴바] -> [도구] -> [Intel Parallel Inspector] -> [Inspect Memory Error] 를 선택하면 대화상자가 나타난다. 이 대화상자는 메모리 오류에 대해서 가벼운 예외 또는 심각한 에러 등을 탐지하는 단계를 설정할 수 있도록 도와준다. 대화상자의 좌측에 나타난 게이지는 설정 단계에 따라 걸리는 시간을 나타낸다.
1단계 : Does my target leak memory?
레벨 1에서는 메로리 누수 유무만을 조사한다.
2단계 : Does my target have memory access problems?
메모리 누수와 함께 다음과 같은 문제의 조사도 시행한다.
이미 해제하여 없는 메모리에 대한 접근과 사용
할당한 메모리의 크기보다 큰 영역에 쓰기를 사용한 경우
힙 메모리의 할당 방법과 해제 방법의 불일치
이미 해제한 메모리의 해제
초기화하지 않은 힙 메모리에서 값을 읽을 경우
3단계 : Where are the memory access problems?
2단계에서 발견된 문제점이 코드 어디에서 발생하고 있는지를 알려준다.
4단계 : Where are all the memory problems Inspector can find?
레벨3 까지의 조사와 함께 다음과 같은 자세한 조사를 시행한다.
정의한 스택 메모리의 크기보다 큰 영역에 쓰기를 한 경우
초기화하지 않은 스택 메모리상에서 값을 읽을 경우
[메뉴바] -> [도구] -> [Intel Parallel Inspector] -> [Inspect Threading Errors]를 선택
스레드 에러 탐지를 선택하면 대화상자가 나타난다.
1단계 : Does my target have deadlocks?
교착상태가 일어날 가능성이 있는 부분을 알려준다.
2단계 : Does my target have deadlocks or data races?
교착상태와 함께 경쟁 상태가 발생할 가능성의 탐지도 실행한다.
3단계 : Where are the deadlocs or data race?
2단계에서 탐지한 버그가 발생하는 프로그램의 위치를 알려준다.
4단계 : Where are all the threading problems Inspector can find?
스레드에 관한 정보와 잠재적 경고를 함께 알려준다.
대화상자 아랫부분에는 [Private suppressiong:]이라는 항목이 있다. 이것은 스레드 에러 탐색에서 제외할 변수에 대해 필터링하는 조건을 설정할 수 있는 항목이다.
Delete problems 필터링 설정된 데이터를 탐색하지 않는다.
Mark problems 필터링 설정된 데이터의 항목만 알려준다.
Do not use suppressions 필터링을 사용하지 않고 전체 변수를 탐색한다.
병목구간( Hotspots ) 탐지, 동시성( Concurrency ) 측정, 록과 록 대기( Lock and Waits ) 측정
[메뉴바] -> [도구] -> [Intel Parallel Amplifier] -> [Hotspots - Where is my program spending time?]을 선택
멀티코어 환경에서 해당 프로젝트를 실행할 때 유휴 CPU가 발생하는 지를 측정하는 것이다. 모든 코어가 쉬지 않고 작업을 실행하는 프로젝트라면 동시성이 높고, 쉬는 CPU가 많다면 동시성이 낮은 프로젝트가 되는 것이다.
[메뉴바] -> [도구] -> [Intel Parallel Amplifier] -> [Concurrency-Where is my concurrency poor?]을 선택
[메뉴바] -> [도구] -> [Intel Parallel Amplifier] -> [Locss and Waits-Where is my waiting?]을 선택