MPI_Scatter
1. 개요
1. 개요
MPI_Scatter는 MPI 표준에서 정의된 핵심적인 집단 통신 함수 중 하나이다. 이 함수의 주요 목적은 병렬 프로그램 실행 시, 하나의 프로세스가 보유한 대량의 데이터를 통신 그룹에 속한 모든 프로세스에 고르게 나누어 배포하는 것이다. 이는 데이터 분산을 통한 병렬 계산의 첫 단계를 구성하는 일반적인 작업으로, 분산 메모리 시스템에서 효율적인 작업 분담의 기초를 제공한다.
함수의 동작은 루트 프로세스로 지정된 하나의 프로세스를 중심으로 이루어진다. 루트 프로세스는 자신의 송신 버퍼에 저장된 데이터를 여러 개의 동일한 크기 조각으로 나눈다. 이후, 통신자에 정의된 프로세스 그룹 내의 각 프로세스 랭크 순서에 따라, 첫 번째 조각은 랭크 0 프로세스로, 두 번째 조각은 랭크 1 프로세스로 전송되는 방식으로 데이터가 분산된다. 이때 모든 프로세스, 심지어 데이터를 보내는 루트 프로세스 자신도 포함하여, 각자는 자신에게 할당된 단일 데이터 조각을 수신 버퍼에 받게 된다.
MPI_Scatter는 MPI_Gather 함수와 정반대의 연산을 수행한다고 볼 수 있다. Gather가 여러 프로세스의 데이터를 한 곳으로 모은다면, Scatter는 한 곳의 데이터를 여러 프로세스로 뿌린다. 또한, 모든 프로세스에게 동일한 데이터를 보내는 MPI_Bcast와는 용도가 구분되며, 데이터 조각의 크기가 프로세스마다 다른 경우에는 MPI_Scatterv 함수를 사용해야 한다. 이 함수는 과학기술계산, 대규모 시뮬레이션 등 데이터 병렬 처리가 요구되는 다양한 고성능 컴퓨팅 응용 분야에서 광범위하게 활용된다.
2. 함수 원형 및 매개변수
2. 함수 원형 및 매개변수
MPI_Scatter 함수의 원형은 다음과 같다.
```c
int MPI_Scatter(const void *sendbuf, int sendcount, MPI_Datatype sendtype,
void *recvbuf, int recvcount, MPI_Datatype recvtype,
int root, MPI_Comm comm)
```
함수의 각 매개변수는 다음과 같은 역할을 한다.
매개변수 | 데이터 타입 | 설명 |
|---|---|---|
|
| 분산할 데이터가 있는 버퍼의 주소. 루트 프로세스에서만 의미가 있으며, 다른 프로세스에서는 무시될 수 있다. |
|
| 각 프로세스로 보낼 데이터 원소의 개수. |
|
|
|
|
| 수신된 데이터 조각을 저장할 버퍼의 주소. 통신 그룹 내 모든 프로세스에서 유효한 출력 매개변수이다. |
|
|
|
|
|
|
|
| 데이터를 분배하는 출발점이 되는 루트 프로세스의 랭크를 지정한다. |
|
| 통신이 이루어지는 커뮤니케이터 (예: |
함수는 성공 시 MPI_SUCCESS를 반환하며, 오류가 발생하면 그에 따른 에러 코드를 반환한다. 이 함수는 집단 통신이므로, 호출하는 모든 프로세스가 동일한 root, comm 매개변수와 호환되는 sendcount, sendtype, recvcount, recvtype 값을 사용하여 참여해야 한다.
3. 작동 방식
3. 작동 방식
MPI_Scatter의 작동 방식은 집단 통신의 대표적인 패턴 중 하나로, 하나의 데이터 소스를 여러 프로세스로 나누어 분배하는 과정을 정의한다. 이 함수는 루트 프로세스가 보내는 버퍼(sendbuf)를 통신자(comm) 내의 모든 프로세스에게 고르게 분할하여 전송한다. 구체적으로, 루트 프로세스의 sendbuf는 sendcount와 sendtype으로 정의된 크기의 데이터 덩어리(청크)들로 나뉜다. 그런 다음, 통신자 내 각 프로세스의 랭크 순서에 따라, 첫 번째 청크는 랭크 0 프로세스로, 두 번째 청크는 랭크 1 프로세스로 전송되는 방식이 반복된다.
모든 프로세스(루트 프로세스 포함)는 이 함수를 호출해야 하며, 각 프로세스는 자신에게 도착한 단일 데이터 청크를 수신 버퍼(recvbuf)에 저장하게 된다. 루트 프로세스의 경우, sendbuf와 recvbuf는 서로 다른 메모리 영역을 가리켜야 하며, sendbuf에서 자신의 랭크에 해당하는 부분이 recvbuf로 복사된다. 이는 MPI_Bcast가 모든 프로세스에 동일한 데이터 전체를 복사하는 것과는 대조적이다. MPI_Scatter는 데이터를 분할하여 각 프로세스에 고유한 부분을 할당하는 데 초점을 맞춘다.
이 작동 방식은 병렬 계산에서 데이터 분산을 수행하는 핵심 메커니즘이다. 예를 들어, 하나의 큰 배열을 여러 프로세스가 각자 담당할 작은 부분으로 나누어 병렬 처리를 할 때 유용하게 사용된다. 모든 프로세스가 동일한 크기의 데이터를 받는다는 점에서 균등 분배에 적합하며, 불균등한 분배가 필요할 때는 MPI_Scatterv 함수를 사용하게 된다.
4. 사용 예제
4. 사용 예제
MPI_Scatter의 사용 예제로, 4개의 프로세스가 동작하는 병렬 프로그램에서 루트 프로세스가 하나의 배열을 나누어 보내는 간단한 시나리오를 살펴본다. 이 예제에서는 루트 프로세스인 랭크 0이 12개의 정수로 구성된 전체 배열을 가지고 있으며, 이를 4개의 프로세스 각각에게 3개의 정수씩 균등하게 분배한다.
다음은 C 언어와 MPI를 사용한 예제 코드의 핵심 부분이다. 모든 프로세스는 자신의 랭크를 확인하고, 루트 프로세스만 전체 데이터 배열을 초기화한다. 그 후 MPI_Scatter 함수가 호출되어 데이터가 분산된다.
```c
#include <mpi.h>
#include <stdio.h>
#define ARRAY_SIZE 12
#define CHUNK_SIZE 3
int main(int argc, char** argv) {
int rank, size;
int send_buffer[ARRAY_SIZE];
int recv_buffer[CHUNK_SIZE];
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
// 프로세스가 4개라고 가정
if (size == 4) {
// 루트 프로세스(랭크 0)만 전체 배열을 준비
if (rank == 0) {
for (int i = 0; i < ARRAY_SIZE; i++) {
send_buffer[i] = i; // 데이터 초기화 (0, 1, 2, ..., 11)
}
}
// 모든 프로세스가 MPI_Scatter를 호출
// 루트(랭크0)의 send_buffer에서 CHUNK_SIZE(3)개씩의 정수를 나누어 보냄
MPI_Scatter(send_buffer, CHUNK_SIZE, MPI_INT,
recv_buffer, CHUNK_SIZE, MPI_INT,
0, MPI_COMM_WORLD);
// 각 프로세스가 받은 데이터 조각을 출력
printf("Process %d received: %d %d %d\n", rank,
recv_buffer[0], recv_buffer[1], recv_buffer[2]);
}
MPI_Finalize();
return 0;
}
```
이 코드를 실행하면 각 프로세스는 다음과 같은 데이터를 수신하게 된다. 데이터는 랭크 순서대로 분배되며, MPI_Scatter는 집단 통신이므로 통신 그룹(MPI_COMM_WORLD) 내의 모든 프로세스가 이 함수를 호출해야 정상적으로 동작한다는 점에 유의해야 한다.
프로세스 랭크 | 수신한 데이터 (recv_buffer) |
|---|---|
0 | 0, 1, 2 |
1 | 3, 4, 5 |
2 | 6, 7, 8 |
3 | 9, 10, 11 |
이 예제는 데이터가 균등하게 분할될 때의 기본적인 사용법을 보여준다. 만약 데이터를 불균등하게 분배해야 한다면, MPI_Scatterv 함수를 사용해야 한다. 또한, 이렇게 분산된 데이터는 각 프로세스에서 독립적인 계산에 사용된 후, 그 결과를 다시 모으기 위해 MPI_Gather 함수가 종종 활용된다.
5. 관련 함수
5. 관련 함수
5.1. MPI_Gather
5.1. MPI_Gather
MPI_Gather는 MPI_Scatter와 반대되는 동작을 수행하는 집단 통신 함수이다. MPI_Scatter가 하나의 루트 프로세스에서 데이터를 여러 프로세스로 분산시킨다면, MPI_Gather는 통신 그룹 내 모든 프로세스로부터 데이터를 수집하여 하나의 루트 프로세스로 모은다. 이는 병렬 계산의 결과를 최종적으로 한 곳에서 취합하거나 요약할 때 필수적으로 사용된다.
함수의 원형은 int MPI_Gather(const void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)이다. 각 프로세스는 자신이 보낼 데이터를 sendbuf에, 보낼 데이터의 개수를 sendcount와 sendtype으로 지정한다. 루트 프로세스만이 의미 있는 recvbuf를 제공하며, 이 버퍼는 다른 모든 프로세스로부터 받은 데이터 조각들을 순서대로 저장할 수 있을 만큼 충분히 커야 한다. recvcount와 recvtype은 각 프로세스로부터 받는 데이터 조각의 크기를 지정하며, 일반적으로 sendcount 및 sendtype과 동일하다.
MPI_Gather의 작동은 다음과 같다. 통신 그룹 내 각 프로세스(랭크 0, 1, 2, ...)는 자신의 sendbuf에서 sendcount만큼의 데이터를 준비한다. 함수 호출이 완료되면, 루트 프로세스의 recvbuf에는 랭크 0 프로세스의 데이터가 가장 먼저 위치하고, 그 뒤를 이어 랭크 1, 랭크 2 프로세스의 데이터가 순차적으로 저장된다. 즉, 수집된 데이터는 프로세스의 랭크 순서에 따라 연속적인 메모리 공간에 배치된다. 루트가 아닌 프로세스에서는 recvbuf, recvcount, recvtype 매개변수가 무시된다.
MPI_Gather는 MPI_Scatterv에 대응하는 MPI_Gatherv 함수와 함께 자주 언급된다. MPI_Gatherv는 각 프로세스로부터 받는 데이터의 크기가 서로 다를 수 있는 불규칙한 데이터 수집을 지원한다. 또한, 모든 프로세스가 수집 결과를 받는 MPI_Allgather 함수도 이와 관련된 중요한 함수이다.
5.2. MPI_Bcast
5.2. MPI_Bcast
MPI_Bcast는 MPI의 집단 통신 함수 중 하나로, 하나의 지정된 루트 프로세스가 가진 데이터를 통신 그룹 내의 모든 다른 프로세스들에게 동일하게 복사하여 전송하는 브로드캐스트 연산을 수행한다. 이 함수는 모든 프로세스가 동일한 데이터를 필요로 하는 시나리오, 예를 들어 모든 프로세스에 동일한 입력 매개변수나 설정 값을 전파할 때 주로 사용된다. MPI_Scatter가 데이터를 분할하여 각 프로세스에 다른 조각을 보내는 것과 달리, MPI_Bcast는 모든 프로세스에게 완전히 동일한 데이터의 복사본을 보낸다는 점에서 차이가 있다.
함수의 원형은 일반적으로 int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm) 형태이다. 여기서 buffer는 루트 프로세스에서는 송신 버퍼, 다른 프로세스에서는 수신 버퍼의 역할을 한다. count와 datatype은 송수신할 데이터의 개수와 자료형을 지정하며, root는 데이터를 보내는 프로세스의 랭크를, comm은 통신이 이루어지는 커뮤니케이터를 의미한다. 모든 프로세스는 동일한 root, count, datatype, comm 인자로 이 함수를 호출해야 정상적으로 동작한다.
MPI_Bcast의 작동은 내부적으로 효율적인 통신 알고리즘을 사용하여 구현된다. 작은 메시지의 경우 트리 기반 알고리즘을, 큰 메시지의 경우 스캐터-개더 방식을 사용하는 등 MPI 구현체에 따라 최적의 경로를 선택하여 데이터를 전파한다. 이로 인해 프로그래머가 직접 루프를 사용하거나 점대점 통신으로 각 프로세스에 데이터를 보내는 것보다 훨씬 효율적이고 간결한 코드 작성이 가능해진다. 이 함수는 병렬 컴퓨팅에서 초기화나 동기화 단계에서 빈번하게 활용된다.
MPI_Bcast와 MPI_Scatter는 모두 데이터를 분배한다는 점에서 유사하지만, 그 목적과 결과는 명확히 다르다. MPI_Scatter는 하나의 큰 데이터 배열을 여러 조각으로 나누어 각 프로세스에 하나씩 할당하는 '분할' 연산인 반면, MPI_Bcast는 하나의 데이터를 모든 프로세스에 '복제'하는 연산이다. 따라서 병렬 처리 패턴을 설계할 때 데이터의 필요 형태에 따라 이 두 함수를 적절히 선택하여 사용해야 한다.
5.3. MPI_Scatterv
5.3. MPI_Scatterv
MPI_Scatterv는 MPI_Scatter 함수의 변형으로, "벡터 스캐터"를 의미한다. 기존의 MPI_Scatter가 모든 프로세스에게 균일한 크기의 데이터를 분산하는 반면, MPI_Scatterv는 각 프로세스마다 서로 다른 양의 데이터를 분산할 수 있는 기능을 제공한다. 이는 데이터의 크기가 불규칙하거나, 작업 부하가 균등하지 않은 병렬 계산 시나리오에서 매우 유용하다. 예를 들어, 행렬의 행을 프로세스에 할당할 때 행의 길이가 다르거나, 입출력 데이터의 크기가 프로세스마다 다른 경우에 적합하다.
함수의 원형은 MPI_Scatter보다 복잡하다. 주요 차이점은 sendcounts와 displs라는 두 개의 추가 배열 매개변수를 사용한다는 점이다. sendcounts 배열은 각 프로세스(랭크 순서대로)에게 보낼 데이터 원소의 개수를 지정하고, displs 배열은 루트 프로세스의 전송 버퍼(sendbuf) 내에서 각 데이터 덩어리가 시작되는 위치의 오프셋을 지정한다. 이를 통해 루트 프로세스는 하나의 큰 버퍼 안에 각기 다른 위치와 크기를 가진 데이터 조각들을 준비하고, 이를 한 번의 집단 통신 호출로 모든 프로세스에 맞춤형으로 분배할 수 있다.
MPI_Scatterv의 작동은 다음과 같다. 먼저, 루트 프로세스는 자신의 sendbuf와 sendcounts, displs 배열을 기반으로 데이터를 패킹한다. 그 후, 통신 그룹 내의 각 프로세스는 자신의 랭크에 해당하는 sendcounts[rank]와 displs[rank] 값에 따라 정해진 데이터 조각을 수신한다. 모든 프로세스는 자신의 수신 버퍼(recvbuf)에 데이터를 저장하며, 수신하는 데이터의 크기는 프로세스마다 다를 수 있다. 이 함수는 집단 통신이므로 그룹 내 모든 프로세스가 참여해야 호출이 완료된다.
MPI_Scatterv는 MPI_Gatherv 함수와 쌍을 이루며, 불균일 데이터의 분산과 수집을 담당한다. 사용 시 주의할 점은 sendcounts와 displs 배열이 루트 프로세스에서만 의미가 있으며, 다른 프로세스에서는 이 매개변수들이 무시될 수 있다는 것이다. 또한, 오프셋 계산 시 사용하는 MPI_Datatype의 크기에 주의해야 하며, 잘못된 오프셋이나 카운트는 세그멘테이션 폴트와 같은 오류를 유발할 수 있다.
6. 주의사항 및 오류 처리
6. 주의사항 및 오류 처리
MPI_Scatter를 사용할 때는 몇 가지 중요한 주의사항을 숙지해야 한다. 가장 핵심적인 점은 모든 프로세스가 동일한 root 프로세스 번호와 comm 통신자로 이 함수를 호출해야 한다는 것이다. 만약 하나의 프로세스라도 호출하지 않거나 다른 매개변수를 사용하면 프로그램은 교착 상태에 빠지거나 예기치 못한 오류를 발생시킨다. 또한, 루트 프로세스가 아닌 다른 프로세스에서는 sendbuf, sendcount, sendtype 매개변수의 값이 무시되지만, 유효한 포인터와 값을 전달해야 언어 바인딩에 따라 문제가 발생하지 않는다.
함수의 반환 값은 오류 코드이며, 성공 시 MPI_SUCCESS를 반환한다. 주요 오류 원인으로는 유효하지 않은 데이터 타입 사용, 버퍼 정렬 문제, sendcount 또는 recvcount가 음수인 경우, 지정한 루트 프로세스가 통신자 내에 존재하지 않는 경우 등이 있다. 이러한 오류는 대부분 MPI_ERR_ARG, MPI_ERR_ROOT, MPI_ERR_COUNT와 같은 표준 오류 코드로 반환된다. 오류 처리를 위해 MPI_Comm_set_errhandler로 오류 처리기를 설정하거나, 반환된 오류 코드를 MPI_Error_string 함수로 해석하는 것이 좋다.
MPI_Scatter는 집단 통신 함수이므로, 각 프로세스가 호출하는 시점이 서로 동기화된다. 이로 인해 성능에 영향을 미칠 수 있으므로, 통신 그룹의 크기가 매우 크거나 데이터 전송량이 많을 경우 비동기 통신이나 다른 최적화 기법을 고려해야 한다. 또한, sendcount는 각 프로세스로 보내는 데이터 요소의 개수를 의미하며, 루트 프로세스의 sendbuf는 정확히 sendcount * (통신 그룹 크기)만큼의 요소를 가지고 있어야 한다. 데이터를 불규칙하게 분산시키고 싶다면 MPI_Scatterv 함수를 사용해야 한다.
7. 여담
7. 여담
MPI_Scatter는 MPI의 핵심적인 집단 통신 함수 중 하나로, 병렬 컴퓨팅의 기본 패턴인 "분할 정복"을 구현하는 데 필수적이다. 하나의 프로세스가 전체 데이터를 보유한 상태에서 각 프로세스가 서로 다른 부분을 처리하도록 작업을 분배할 때 이 함수가 빈번히 사용된다. 이는 데이터 병렬 처리의 전형적인 시작점을 제공한다.
MPI_Scatter와 그 변형 함수인 MPI_Scatterv의 관계는 흥미롭다. MPI_Scatter는 모든 프로세스가 동일한 크기의 데이터 조각을 받을 것을 전제로 하는 반면, MPI_Scatterv는 프로세스마다 다른 크기의 데이터를 분산시킬 수 있다. 이는 처리할 워크로드가 균일하지 않거나 데이터 구조가 불규칙할 때 매우 유용하다. 예를 들어, 행렬 연산에서 각 행의 길이가 다르다면 MPI_Scatterv가 더 적합한 선택이 될 수 있다.
이 함수의 작동 방식을 이해하는 것은 MPI 프로그래밍의 기초를 다지는 데 중요하다. MPI_Scatter는 단순히 데이터를 보내는 것을 넘어, 통신 그룹 내 모든 프로세스가 조율된 방식으로 동시에 참여하는 집합 연산이다. 따라서 한 프로세스에서만 이 함수를 호출하는 것은 의미가 없으며, 통신자(comm)에 포함된 모든 프로세스가 동일한 root를 지정하여 호출해야 한다. 이는 MPI_Bcast가 모든 프로세스에 동일한 데이터를 복사하는 것과는 대비되는, 진정한 '분산'의 의미를 담고 있다.
