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

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
    • C, C++, FORTRAN
  • 프로그래밍 모델
    • 아래 두가지 메모리 아키텍처 타입을 모두 지원하여 무엇을 사용할지 정하면 된다
  • 어떤 하드웨어 플랫폼에서도 동작 가능하다
    • 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

  • 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

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 함수도 존재
    • Pair의 array
    • Matrix

Basic MPI Data Types

MPI Tags

  • 메시지는 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
      • Where the message is
    • count: number of data items
    • datatype: type of data items
    • dest: rank of destination process
    • tag: message tag
      • Tag로 약속해둔 값
    • comm: communicator, usually MPI_COMM_WORLD

MPI_Send() is blocking

  • 데이터가 시스템으로 전달되었음은 보장
    • 해당 함수 호출이 끝나면 송신 버퍼에 새로운 내용을 넣어도 됨
  • 목적지 프로세스가 해당 데이터를 실제로 받았는지는 확인하지 않을 수 있음
    • 언제 리턴할지는 MPI 구현에 따라 다르기 때문!
    • 어떤 구현은 sender의 송신 버퍼에 쓰기만 완료하고 리턴할 수도 있다
      • 실제로 받았는지는 모름
    • 어떤 구현은 반대쪽에 알맞은 recv가 있을 때 리턴하기도 한다
      • 실제로 받을때까지 리턴하지 않음
      • 이는 데드락 발생 조건이 됨

Blocking Receive

  • MPI_Recv() is blocking
  • 시스템에 해당 메시지를 받을 때까지 기다렸다가 리턴
  • 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
    • 구현에 따라 아래와 같은 함수가 데드락을 발생시킬 수 있음

Buffering

  • Send - Recv 가 비동기적으로 발생할 수 있다
    • 구현에 따라 다름
  • 따라서 값을 잡고있을 시스템 버퍼가 필요

Communication Modes for Send

  • MPI_Send()
    • 버퍼: 시스템 마음대로
      • 데이터가 크면 사용 X, 작으면 사용 O
    • 블로킹: 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?

  • 하나밖에 없음
  • MPI_Recv

Blocking vs Synchronous

  • Blocking: 어딘가에 쓴 다음(송신 버퍼 등) 리턴
    • Non-blocking: 쓰겠다는 의사만 전달 후 바로 리턴
  • Synchronous: 상대방 프로세스가 명시적으로 받은 다음 리턴
    • Non-synchronous: 관계 없이 리턴

Delivery Order

  • 규칙
    1. Sender가 두 개의 메시지를 연속으로 동일한 destination에 보내면 첫번째부터 받는다
    2. Receiver가 두 개의 receive를 연속으로 post했고, 둘 다 동일한 메시지에 대한 것이라면 첫번째 receive가 받는다
    3. 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: 완료 확인 후 리턴
  • 데드락 해결하는 예제

Collective Communications

  • 모든 communicator 내 프로세스간의 통신

주요 함수

  • MPI_Barrier(MPI_Comm comm)

  • MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int source, MPI_Comm comm)

    • Send 대체
    • 동일한 값 나눠주기
  • MPI_Reduce(void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int target, MPI_Comm comm)

    • Receive 대체
    • 결과값을 호출자에게 합치는 통신
  • MPI_Gather(void *sendbuf, int sendcount, MPI_Datatype datatype, void *recvbuf, ..., int target, MPI_Comm comm)

    • 결과값 호출자에게 보내주기
  • MPI_Scatter(void *sendbuf, int sendcount, MPI_Datatype datatype, void *recvbuf, ..., int source, MPI_Comm comm)

    • 각 부분 나눠주기
  • MPI_Alltoall(void *sendbuf, int sendcount, MPI_Datatype datatype, void *recvbuf, ..., int source, MPI_Comm comm)

    • Transpose

예제 ) 계산하기

  • 각자 1/n 의 적분을 계산
  • rank 0은 우선 n을 다 알려줘야
  • 각 프로세스는 1/n을 계산 (rank/n ~ rank/n+1/n)
  • Reduction으로 합하기