SMP
- Symmetric multiprocessing or Shared-memory multiprocessing (SMP)
- 두 개 이상의 동일한(identical) 프로세서가 하나의 shared main memory에 연결되어 모든 입력과 출력 디바이스에 접근 가능한 멀티프로세서 컴퓨터 하드웨어 및 소프트웨어 아키텍처
- 프로세서는 하나의 OS instance에 의해 제어되며, 모든 프로세서가 동일하게 컨트롤된다
- 대부분의 멀티프로세서 시스템이 사용하는 구조
- 멀티코어의 경우, SMP 구조가 코어별로 적용되어 서로 다른 코어를 서로 다른 프로세서처럼 대한다
- 하나의 노드의 경우 그 내부가 SMP 구조로 되어있음
- 여러개의 프로세서가 메인 메모리를 접근하는 시간차에서 나온 개념이 UMA와 NUMA
- NUMA(Non-Uniform Memory Access) : 각 프로세서 노드에 자체적인 메모리(local memory)가 할당됨
- 자기 로컬 메모리 접근은 빠르고, 다른 프로세서의 메모리(remote memory)에 접근할 때는 느림
- CPU 소켓이 두개라서 우리 서버도 NUMA이다. 다른 CPU의 메인 메모리는 접근하기가 빡세기 때문
- UMA(Uniform Memory Access) : 모든 CPU가 공유 메모리를 사용하여 모든 CPU가 메모리에 접근하는 속도가 동일
- SMP
-Scalable-High-Performance-Computing/images/SHPC-15-1.png)
MPI
- Message Passing Interface
- 여러 메시지를 전달하는 프로그램(message-passing program == 프로세스) 간의 메시징 전송 표준
- Portable, efficient, flexible
- 대부분의 HPC 플랫폼이 지원
- Distributed Memory (Cluster)
- Shared Memory (SMP)
- Hybrid (SMP + Cluster)
- 지금까지의 커뮤니케이션 라이브러리 중 가장 빠름
- 기능도 매우 많음
- OpenMP와 다르게 explicit parallelism
- 어느 부분이 메시지를 보내는 포인트이고 어디가 받는 포인트인지 모두 알 수 있음
- Parallel computer, cluster, heterogeneous network에서 사용됨
- 메시지를 전달하는 라이브러리 스펙 제공
- Language bindings
- 프로그래밍 모델
- 아래 두가지 메모리 아키텍처 타입을 모두 지원하여 무엇을 사용할지 정하면 된다
-Scalable-High-Performance-Computing/images/SHPC-15-3.png)
- 어떤 하드웨어 플랫폼에서도 동작 가능하다
- Distributed Memory
- Shared Memory
- Hybrid
- MPI programming model은 distributed memory model을 이용하지만, 하드웨어가 어떻든 관계없이 사용 가능
- 모든 parallelism은 명시적
- Message passing model 사용
- Inter-process communication을 위해
- 각 프로세스가 각자의 address space를 갖고 있어야 함
- Synchronization 및 각자의 address에서 다른 address로 데이터를 이동하는 것이 필요
- 각 프로세스가 협력해야 함
- Explicit한 전송
- Point-to-point 커뮤니케이션
- 받는 프로세스의 메모리 변경은 반드시 받는 프로세스의 명시적인 참여로 발생
- 몇개의 CPU(여기서는 uniprocessor에서의 설명이므로 core)를 사용할지 프로그램의 시작부에 정적으로 정의해야 함
- MPI2는 dynamic process creation을 허용하나 사용이 제한적임
- 지금 MPI 프로세스랑 CPU랑 1대 1 대응된다고 생각하는 것 (일반적으로 맞음)
- SPMD를 사용하며 MPMD도 있긴한데 잘 안쓴다
MPI Program Structure
- MPI include file
- MPI environment initialization
- Message passing calls
- MPI environment termination
Hello, World
-Scalable-High-Performance-Computing/images/SHPC-15-4.png)
- MPI_COMM_WORLD : Default MPI communicator
- MPI communicator : 통신하는 프로세스의 집합
- 모든 프로세스의 집합
- 설정하지 않으면 자동으로 default, 즉 MPI_COMM_WORLD로 설정됨
- MPI_Comm_rank() : communicator 에서 나의 ID를 가져옴
- MPI_Comm_size() : communicator에 들어있는 프로세스의 개수
- Print 결과 - 순서가 다른데 (OS의 CFS 알고리즘에 의해) 스케쥴링이 되기 때문에 미리 알 수 없다.
Running MPI Programs
- MPI 2에서는
mpiexec <args>
로 실행
- 그러나 구현에 따라 다르기 때문에 확인하고 실행하면 됨
- e.g.
mpicc -o hello hello.c
(컴파일)
- e.g.
mpirun np 4 ./hello
(실행. 4개 프로세서)
MPI Naming Conventions
- All names have MPI_ prefix
- In C: mixed uppercase/lowercase
- ierr = MPI_XXxx(arg1, arg2, …);
- MPI constants are all uppercase
- MPI_COMM_WORLD, MPI_SUCCESS, MPI_DOUBLE, MPI_SUM, …
Error Handling
- 에러는 모든 프로세스를 abort 시킴
- 에러코드로 routine 설정 가능
- C++에서는 exception이 던져짐
- ierr = MPI_XXxx(arg1, arg2, …); 를 호출했을 때 ierr == MPI_SUCCESS 이면 괜찮음
Environment
- 프로세스는 0~(Comm size-1) 사이의 값을 가진다
- 어느 communicator를 기준으로 하는지에 따라 달라짐
- Communicator
- 서로 통신가능한 프로세스의 집합
MPI_COMM_WORLD
가 가장 기본적
- Pre-defined, 모든 프로세스를 포함함
- 이 아래에 subgroup, subcommunications를 둘 수 있음
- 관련 함수
MPI_Comm_size(MPI_COMM_WORLD, &size)
: MPI_COMM_WORLD의 프로세스의 수
MPI_Comm_rank(MPI_COMM_WORLD, &rank)
: MPI_COMM_WORLD에서의 프로세스 id
MPI Initialization and Termination
-Scalable-High-Performance-Computing/images/SHPC-15-5.png)
MPI Initialization
int MPI_Init(int *argc, char **argv)
- MPI 환경 초기화
- MPI 루틴이 호출되기 전에 반드시 호출되어야 함
- 한번만 호출되어야 함
int MPI_Initialized(int *flag)
- MPI_Init 이 호출되었는지 확인
- 플래그가 0이면 호출되지 않은 거
MPI Termination
int MPI_Finalize(void)
- MPI 환경을 정리
- 프로그램 종료 전 반드시 호출
- 다른 MPI 루틴은 해당 호출 이후에 호출될 수 없음
- MPI_Init 포함
- MPI_Initialized, MPI_Get_version, MPI_Finalized 만 빼고
int MPI_Abort(MPI_Comm comm, int errcode)
- Communicator 내의 모든 프로세스를 끝낸다
- 모든 태스크를 abort 시키느라 시간이 오래 걸림
MPI Communications
- Point-to_point 커뮤니케이션
- 전송자와 수신자
- 프로세스와 프로세스 간 커뮤니케이션
- Collective communications
- Communicator 내의 모든 프로세스가 참여
- Barrier, reduction operations, gather 등
MPI Data types
- 여러 타입이 존재함. 대부분 가능하다 생각하면 될 듯
- Predefined
- e.g.,MPI_INT,MPI_DOUBLE_PRECISION
- MPI datatype의 배열
- Datatype의 strided block
- Datatype block의 Indexed array
- Data type의 임의 구조
- 커스템 데이터타입을 만들기 위한 MPI 함수도 존재
Basic MPI Data Types
-Scalable-High-Performance-Computing/images/SHPC-15-6.png)
- 메시지는 user-defined integer tag와 함께 보내짐
- 받는 프로세스가 메시지를 식별하게 하기 위해
- 혹은 필터링에 사용될 수도 있음
- MPI_Recv 를 할때 특정 태그를 명시하면 해당 태그가 있는 메시지만 수신
- MPI_ANY_TAG 를 통해 필터링 없이 수신할 수도 있음
Blocking Send
- 전송이 끝날때까지 리턴하지 않는 메시지 전송 함수
int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
buf
: address of send buffer
count
: number of data items
datatype
: type of data items
dest
: rank of destination process
tag
: message tag
comm
: communicator, usually MPI_COMM_WORLD
MPI_Send()
is blocking
- 데이터가 시스템으로 전달되었음은 보장
- 해당 함수 호출이 끝나면 송신 버퍼에 새로운 내용을 넣어도 됨
- 목적지 프로세스가 해당 데이터를 실제로 받았는지는 확인하지 않을 수 있음
- 언제 리턴할지는 MPI 구현에 따라 다르기 때문!
- 어떤 구현은 sender의 송신 버퍼에 쓰기만 완료하고 리턴할 수도 있다
- 어떤 구현은 반대쪽에 알맞은 recv가 있을 때 리턴하기도 한다
- 실제로 받을때까지 리턴하지 않음
- 이는 데드락 발생 조건이 됨
Blocking Receive
- MPI_Recv() is blocking
- 시스템에 해당 메시지를 받을 때까지 기다렸다가 리턴
-Scalable-High-Performance-Computing/images/SHPC-15-7.png)
- MPI_Recv Status
- status.MPI_TAG – tag of received message
- status.MPI_SOURCE – source rank of message
- status.MPI_ERROR – error code
- 메시지의 길이 정보도 담겨있는데, 별도 함수로 접근한다
int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count)
Deadlocks
- Must be careful to avoid deadlock
- 구현에 따라 아래와 같은 함수가 데드락을 발생시킬 수 있음
-Scalable-High-Performance-Computing/images/SHPC-15-8.png)
Buffering
- Send - Recv 가 비동기적으로 발생할 수 있다
- 따라서 값을 잡고있을 시스템 버퍼가 필요
Communication Modes for Send
- MPI_Send()
- 버퍼: 시스템 마음대로
- 블로킹: O
- 동기 여부: 구현마다 다름
- Blocking
- MPI_Bsend()
- 버퍼: 사용
- 블로킹: O
- 동기 여부: X, sender 버퍼에 쓴 후 리턴
- Buffering, Blocking, Not synchronous
- MPI_Ssend()
- 버퍼: X
- 블로킹: O
- 동기 여부: O, 상대쪽에서 receive를 한 후 리턴
- No Buffering, Blocking, Synchronous
- MPI_Rsend()
- matching receive가 이미 post된 경우에만 사용 가능
- 아니면 에러 발생
Modes for Receive?
Blocking vs Synchronous
- Blocking: 어딘가에 쓴 다음(송신 버퍼 등) 리턴
- Non-blocking: 쓰겠다는 의사만 전달 후 바로 리턴
- Synchronous: 상대방 프로세스가 명시적으로 받은 다음 리턴
- Non-synchronous: 관계 없이 리턴
-Scalable-High-Performance-Computing/images/SHPC-15-9.png)
Delivery Order
- 규칙
- Sender가 두 개의 메시지를 연속으로 동일한 destination에 보내면 첫번째부터 받는다
- Receiver가 두 개의 receive를 연속으로 post했고, 둘 다 동일한 메시지에 대한 것이라면 첫번째 receive가 받는다
- Receive 요청이 두 Sender의 메시지에 다 match되면, 뭘 받을지 모른다. 아무거나 하나 받는다.
Send-Receive
- Deadlock을 방지하기 위해 사용
MPI_Sendrecv
- 내부적으로 non-blocking send와 non-blocking recv를 실행하고 둘이 끝나기를 대기
Non-blocking Communication
- 메시지가 보내졌거나/받아졌는지에 관계없이 리턴
- 장점
- Idle time, deadlock을 피할 수 있다
- Computation과 communication을 겹쳐서 진행 가능
- MPI_Isend, MPI_Irecv, MPI_Wait
- MPI_Test: 완료 확인 후 리턴
- 데드락 해결하는 예제
-Scalable-High-Performance-Computing/images/SHPC-15-11.png)
Collective Communications
- 모든 communicator 내 프로세스간의 통신
주요 함수
-
MPI_Barrier(MPI_Comm comm)
-
MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int source, MPI_Comm comm)
- Send 대체
- 동일한 값 나눠주기
-Scalable-High-Performance-Computing/images/SHPC-09-13.png)
-
MPI_Reduce(void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int target, MPI_Comm comm)
- Receive 대체
- 결과값을 호출자에게 합치는 통신
-Scalable-High-Performance-Computing/images/SHPC-09-16.png)
-
MPI_Gather(void *sendbuf, int sendcount, MPI_Datatype datatype, void *recvbuf, ..., int target, MPI_Comm comm)
- 결과값 호출자에게 보내주기
-Scalable-High-Performance-Computing/images/SHPC-09-13.png)
-
MPI_Scatter(void *sendbuf, int sendcount, MPI_Datatype datatype, void *recvbuf, ..., int source, MPI_Comm comm)
- 각 부분 나눠주기
-Scalable-High-Performance-Computing/images/SHPC-09-13.png)
-
MPI_Alltoall(void *sendbuf, int sendcount, MPI_Datatype datatype, void *recvbuf, ..., int source, MPI_Comm comm)
- Transpose
-Scalable-High-Performance-Computing/images/SHPC-09-15.png)
예제 ) π 계산하기
- 각자 1/n 의 적분을 계산
- rank 0은 우선 n을 다 알려줘야
- 각 프로세스는 1/n을 계산 (rank/n ~ rank/n+1/n)
- Reduction으로 합하기
-Scalable-High-Performance-Computing/images/SHPC-15-13.png)