본문 바로가기

Computer & Parallel Processing

OpenMP 병렬 처리 : 공유변수 문제 (레이스 컨디션) 해결

반응형

드디어 세 번째 포스팅 공유 변수 문제의 해결입니다.

앞서 포스팅에서는 OpenMP를 이용할 때 공유 변수 문제를 다루었죠.

2020.10.19 - [Computer & Parallel Processing] - OpneMP 병렬 처리 : 공유변수 문제 (레이스 컨디션)

 

OpneMP 병렬 처리 : 공유변수 문제 (레이스 컨디션)

2020/10/13 - [Computer & Parallel Processing] - OpenMP를 이용한 병렬 처리 (parallel for) OpenMP를 이용한 병렬처리 (parallel for) OpenMP를 이용한 병렬 처리 기법은 다양한 방법이 있습니다. 그중 반복문..

sbinroom.tistory.com

이번에는 해당 문제를 해결해 보겠습니다. 사실 첫 포스팅부터 해당 부분이 해결된 코드를 이미 보여 드렸습니다. 아래 스냅숏과 같은 코드였죠.

일단 flag부터 보겠습니다. flag의 문제점은 다수의 프로세서가 동시 동작하면서, flag의 상태 값이 예기치 않게 변화한다는 것이었습니다. 이 문제는 간단히 해결됩니다. 하나의 변수를 다수의 프로세서가 접근한 게 문제이니, 각 프로세서에게 각자가 사용할 변수를 지정해 주는 거죠. 그 키워드가 private입니다.

위 코드의 20번째 줄 마지막에 private(flag)를 지정했죠. 이 키워드로 인해 해당 for문은 구동하며 각 프로세서마다 따로 사용할 flag를 할당합니다. 이렇게 되면 1번 프로세서와 2번 프로세서가 사용하는 flag는 서로 다른 flag가 되는 거죠. 이로서 레이스 컨디션 문제점이 해결됩니다. 이로 인해 변경된 구조는 아래 플로우 차트와 같습니다.

각 프로세서는 전역으로 만들어진 flag가 아니라 private(flag)로 인해 각기 생성된 flag에 접근합니다. 이로서 공유 변수를 독립 변수로 바꾸어 문제를 해결하죠.

 

그럼 두 번째 문제로 넘어가죠. res입니다. 이경우에는 위와 같은 방식으로 처리할 수가 없습니다. 왜냐면 res는 소수의 개수를 세는 전역 변수이니까요. 이 변수를 private으로 설정한 코드와 결과는 아래 스냅숏과 같습니다.

구동 결과를 보면 serial 프로세싱 시 25의 결과가 나와야 하지만 parallel 프로세싱의 결과는 0입니다. 그 이유는 프로그램이 아래 플로우 차트와 같이 구동되기 때문입니다.

각 프로세서에는 flag와 같이 독립적으로 할당받은 res(살구색)가 생기고, 각 프로세서는 해당 res를 업데이트하는 방식으로 구동됩니다. 그런데 문제는 보라색 res입니다. 코드의 35번째 줄에서 접근하는 res는 보라색 res이며, 살구색 res는 for문이 끝났을 때 자동으로 삭제됩니다. 그 결과 프로그램이 출력하는 값은 0이 되는 거죠.

줄이면 전역 변수를 업데이트해야 하지만 지역변수를 업데이트해서 결과가 틀리게 나온 것이죠.

이 문제를 해결하는 방법은 두 가지입니다.

그중 앞서 포스팅에서 사용한 critical을 먼저 설명하겠습니다. critical은 thread에서 이용되는 mutex와 같은 역할을 수행한다고 생각하시면 됩니다. mutex와 같이 필요한 영역에서 병렬 처리를 강제로 serial 프로세싱으로 변경합니다. 프로그램은 아래 플로우 차트와 같이 변경됩니다.

각 프로세서에 붉은색 박스가 생겼죠. 각 프로세서는 붉은색 박스에 들어갈 때 다른 프로세서가 해당 영역에 있는지 먼저 확인합니다. 만약 해당 영역에 있는 프로세서가 있을 경우 대기하고, 없으면 해당 영역을 구동합니다. 정확히 멀티 스레드 프로그램의 mutex와 같은 역할이죠. 이 방법은 많이 사용하고 단순하지만, 병렬 처리되어도 되는 부분은 serial로 처리하는 단점이 있습니다. 그래서 두 번째 방법이 이로울 때가 많습니다.

두번째 방법은 reduction 키워드를 이용하는 것입니다. reduction은 private의 확장 키워드라고 생각하시면 쉽게 이해할 수 있습니다. reduction으로 지정된 변수가 있다면, private으로 지정된 변수와 같이 각 프로세서가 지역 변수를 생성해서 이용합니다. private과 다른 점은 병렬 처리가 끝났을 때, 지정된 키워드로 지역변수가 통합되어 전역 변수에 적용됩니다. 결과적으로 프로그램은 아래와 같은 플로우 차트 형태로 변경됩니다.

for문이 종료되면 살구색 res의 값을 취합하여, 보라색 res의 값을 업데이트합니다. 코드 상으로는 아래 스냅숏과 같이 변경됩니다.

reduction 키워드는 다양한 연산자를 사용할 수 있습니다. 가능한 연산자 종류는 +, -, *, &, |, ^, && and ||입니다. / 연산은 없습니다. 논리적으로 생각해 보면 당연합니다. a + b + c는 (a + b) + c 와 a + (b + c)의 결과 가 같지만 a / b / c 는 (a / b) /c 와 a / (b / c) 의 결과가 다릅니다. 이렇게 순서가 변경되어도 결과가 같은 연산자 들만 reduction에서 이용 가능합니다.

 

예시로 만든 프로그램은 두 가지 방식 중 reduction을 이용하는 게 더 효과적인 구조입니다. 왜냐하면, 아무리 짧은 구간이라고 해도 critical 영역을 이용하면 그 영역은 serial 프로세싱으로 변경되기 때문이죠. 반면 reduction은 모든 연산이 종료되었을 때 취합히기 때문에 더 효율적이라고 할 수 있습니다. (단 추가적인 메모리 사용 단점이 있습니다.)

 

그렇다고 critical 보다 reduction이 항상 우수한 것은 아닙니다. 위 예제에서는 겨우 하나의 변수가 reduction 되지만, 필요한 경우 큰 사이즈의 변수가 reduction 되어야 할 수도 있고 이경우 증가하는 메모리 사용량은 무시할 수 없는 수준이기 때문입니다.

 

그럼 이번 포스팅은 여기서 마치겠습니다. 다음 포스팅에서는 schedule을 다루려고 합니다.

 

감사합니다.

반응형