Sử dụng thư viện MPI

MPI là gì?

M P I = Message Passing Interface, là đặc tả cho người phát triển và thư viện lập trình truyền thông message sử dụng trong tính toán song song. Tập lệnh MPI thực thi bao gồm thư viện các thủ tục sao cho có thể gọi được từ các chương trình Fortran, C, C++ hay Ada.


Mô hình lập trình

  • Ban đầu MPI được thiết kế cho các kiến trúc bộ nhớ phân tán, kiến trúc rất phổ biến thời kỳ 1980 đến đầu năm 1990.distributed_mem
  • Xu hướng công nghệ thay đổi,  bộ nhớ chia sẻ kết hợp với mạng máy tính tạo ra dạng lai của hai hệ thống bộ nhớ chia sẻ và bộ nhớ phân tán.
  • Thực thi MPI tương thích với cả hai kiểu kiến trúc trên và cũng tương thích với các kiểu kết nối và giao thức khác nhau.hybrid_mem
  • Ngày nay MPI có thể chạy trên hầu hết các nền tảng phần cứng:
    • Bộ nhớ chia sẻ
    • Bộ nhớ phân tán
    • Dạng lai hai loại trên

Cấu trúc chương trình MPI

Cấu trúc chương trình MPI

Ví dụ 1: Khởi tạo môi trường, in ra id của các tác vụ


#include "mpi.h"
#include "stdio.h"
int main(int argc, char * argv[])
{
	//Khoi tao moi truong cho MPI
	MPI_Init(&argc,&argv);
        int numtasks;//Chua so process trong group
	int idtask;//Chua gia tri id cua moi process
	MPI_Comm_size(MPI_COMM_WORLD,&numtasks);
	MPI_Comm_rank(MPI_COMM_WORLD,&idtask);
        //in ra id cua cac tac vu va so tac vu khoi tao
        printf("Id tac vu: %d trong tong so: %d\n",idtask, numtasks);
	//Giai phong moi truong MPI
	MPI_Finalize();
	return 0;
}

Tham khảo cách tích hợp MPI trong Visual Studio và cấu hình


Nhóm (group) và Comunicator

  • Một nhóm là một tập có thế tự của các tiến trình. Mỗi tiến trình trong một nhóm được gắn với một số id duy nhất (rank). Các giá trị của rank bắt đầu từ 0 tới N-1, ở đây N là số tiến trình trong nhóm. Trong MPI, một nhóm được đại diện trong bộ nhớ hệ thống như một đối tượng. Nó có thể truy cập bởi các lập trình viện chỉ qua “handle”. Một nhóm luôn luôn gắn với một đối tượng communicator.
  • Một communicator liên quan đến một nhóm các tiến trình mà có thể giao tiếp với nhau. Tất cả các message MPI phải chỉ định bởi một communicator. Giống như group, các communicator được mô tả trong bộ nhớ như các đối tượng và được truy cập bởi lập trình viên chỉ qua các “handle”. Ví dụ, handle cho một communicator là bao gồm tất cả các tác vụ của MPI_COMM_WORLD.
  • Về khía cạnh lập trình viên, một nhóm và một communicator là một.

comm_group600pix

Ví dụ 2: Từ nhóm ban đầu, chia thành 2 nhóm riêng biệt và thực hiện truyền thông trên mỗi nhóm


#include "mpi.h"
#include "stdio.h"
#define NPROCS 8

int main(int argc, char *argv[])  {
int        rank, new_rank, sendbuf, recvbuf, numtasks,
           ranks1[4]={0,1,2,3}, ranks2[4]={4,5,6,7};
MPI_Group  orig_group, new_group;
MPI_Comm   new_comm;
//Khoi tao moi truong MPI
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);

if (numtasks != NPROCS) {
  printf("So tac vu nen = %d. Ket thuc.\n",NPROCS);
  MPI_Finalize();
  }

sendbuf = rank;

/* Lay ra handle cua nhom goc*/
MPI_Comm_group(MPI_COMM_WORLD, &orig_group);

/* Phan chi thanh 2 nhom rieng biet dua tren rank */
if (rank < NPROCS/2) {
  MPI_Group_incl(orig_group, NPROCS/2, ranks1, &new_group);
  }
else {
  MPI_Group_incl(orig_group, NPROCS/2, ranks2, &new_group);
  }

/* Tao mot communicator và thu thi cac truyen thong tap hop */
MPI_Comm_create(MPI_COMM_WORLD, new_group, &new_comm);
MPI_Allreduce(&sendbuf, &recvbuf, 1, MPI_INT, MPI_SUM, new_comm);

MPI_Group_rank (new_group, &new_rank);
printf("Id task= %d newrank= %d recvbuf= %d\n",rank,new_rank,recvbuf);

MPI_Finalize();
}

Truyền thông từ điểm tới điểm

Ví dụ 3: Thực hiện truyền thông có khoá gửi message ‘x’, ‘y’ từ tác vụ có id = 0 tới tác vụ 1 và ngược lại


#include "mpi.h"
#include "stdio.h"
int main(int argc, char* argv[])
{
        int noProc, idProc;
	char inmsg, outmsg = 'x';
	int dest, source,tag = 1;
	MPI_Status Stat;
	MPI_Init(&argc,&argv);
	MPI_Comm_size(MPI_COMM_WORLD,&noProc);
	MPI_Comm_rank(MPI_COMM_WORLD,&idProc);
	//Gui msg tu tac vu co id = 0 toi tac vu co id = 1
	if(idProc == 0)
	{
		//gui du lieu 'x' toi tac vu co id = 1
		dest = 1;
		source = 1;
		MPI_Send(&outmsg,1,MPI_CHAR,dest,tag,MPI_COMM_WORLD);
                //nhan du lieu 'y' tu tac vu co id = 1
		MPI_Recv(&inmsg,1,MPI_CHAR,source,tag,MPI_COMM_WORLD,&Stat);
		printf("ProcessID: %d, Message da nhan duoc:%c\n",idProc,inmsg);
	}
	if(idProc == 1)
	{
        
		source = 0;
		dest = 0;
                //nhan du lieu 'x' tu tac vu co id = 0
		MPI_Recv(&inmsg,1,MPI_CHAR,source,tag,MPI_COMM_WORLD,&Stat);
		printf("ProcessID: %d, Message da nhan duoc:%c\n",idProc,inmsg);
		outmsg = 'y';
                //gui du lieu 'y' toi tac vu co id = 0
		MPI_Send(&outmsg,1,MPI_CHAR,dest,tag,MPI_COMM_WORLD);
	}
	//Giai phong MPI
	MPI_Finalize();
	return 0;
}

Ví dụ 4: Thực hiện truyền thông không khoá, gửi message vòng tròn từ tác vụ có id 0 -> 1 …-> n-1 và ngược lại


#include "stdio.h"
#include "mpi.h"
int main(int argc, char* argv[])
{
	int noProc, idProc, next, prev, tag1 = 1, tag2 = 2;
	char inmsg[2], outmsg[2];
	MPI_Request reqs[4];
	MPI_Status stats[4];
	outmsg[0] = 'x';
	outmsg[1] = 'y';
        //Khoi tao moi truong MPI su dung cac doi so mac dinh cua ham main
	MPI_Init(&argc,&argv);
	MPI_Comm_size(MPI_COMM_WORLD,&noProc);
	MPI_Comm_rank(MPI_COMM_WORLD, &idProc);
	next = idProc + 1;
	prev = idProc - 1;
	if(idProc == 0) prev = noProc - 1;
	if(idProc == (noProc -1)) next = 0;
        //gui va nhan du lieu theo kieu non-blocking
	MPI_Isend(&outmsg[0],1,MPI_CHAR,next,tag1,MPI_COMM_WORLD,&reqs[0]);
	MPI_Irecv(&inmsg[0],1,MPI_CHAR,prev,tag1,MPI_COMM_WORLD, &reqs[1]);

	MPI_Irecv(&inmsg[1],1,MPI_CHAR,next,tag2,MPI_COMM_WORLD, &reqs[2]);
	MPI_Isend(&outmsg[1],1,MPI_CHAR,prev,tag2,MPI_COMM_WORLD, &reqs[3]);
        //doi cac tien trinh nhan va gui message ket thuc
	//MPI_Wait(&reqs,&stats);
	MPI_Waitall(4,reqs,stats);
	
	printf("ProcessId:%d, Message tag1:%c\n",idProc, inmsg[0]);
	printf("ProcessId:%d, Message tag2:%c\n",idProc, inmsg[1]);
    
	MPI_Finalize();
	return 0;
}
Giải thích

Đoạn code trên thực hiện gửi message vòng tròn từ giữa các tác vụ, ví tụ tác vụ có id là i gửi dữ liệu tới tác vụ i+1 và nhận message từ i-1 và ngược lại.
ringtopo


Truyền thông tập hợp

Truyền thông tập hợp bao gồm tất cả  các tiến trình (process) trong phạm vi của một commnicator.

  • Tất cả các tiến trình là mặc định, các thành viên nằm trong một communicator MPI_COMM_WORLD.
  • Bổ xung thêm các comminicators có thể được định nghĩa bởi lập trình viên.
  • Truyền thông tập hợp có thể có kết quả không mong muốn, trong đó chương trình có thể thất bại có thể xảy ra nếu một tác vụ trong môt một communicator không tham dự.

Các kiểu truyền thông tập hợp:collective_comm

  • Đồng bộ – Các tiến trình đợi đến khi tất cả các thành viên trong nhóm đạt tới điểm đồng bộ.
  • Di chuyển dữ liệu – broadcast, scatter/gather, all to all.
  • Tính toán truyền thông (reductions) – một thành viên của nhóm thu thập dữ liệu từ các thành viên khác và thực hiện các thao tác (min, max, cộng, nhân, v.v.) trên dữ liệu.

Ví dụ 5: Thực hiện truyền thông tập hợp gửi dữ liệu từ một tác tới các tác vụ còn lại


#include "mpi.h"
#include "stdio.h"
#define SIZE 4

int main(int argc, char* argv[])
{
int numtasks, rank, sendcount, recvcount, source;
float sendbuf[SIZE][SIZE] = {
  {1.0, 2.0, 3.0, 4.0},
  {5.0, 6.0, 7.0, 8.0},
  {9.0, 10.0, 11.0, 12.0},
  {13.0, 14.0, 15.0, 16.0}  };
float recvbuf[SIZE];
//Khoi tao moi truong
MPI_Init(&argc,&argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);

if (numtasks == SIZE) {
  source = 1;
  sendcount = SIZE;
  recvcount = SIZE;
  //Gui du lieu tu tac vu co id = 1 toi cac tac vu khac
  MPI_Scatter(sendbuf,sendcount,MPI_FLOAT,recvbuf,recvcount,
             MPI_FLOAT,source,MPI_COMM_WORLD);
  //In ra du lieu trong cac tac vu
  printf("Id task= %d  ket qua: %f %f %f %f\n",rank,recvbuf[0],
         recvbuf[1],recvbuf[2],recvbuf[3]);
  }
else
  printf("So tac su dung nen bang: %d. Ket thuc.\n",SIZE);

MPI_Finalize();
}

Bài tập lập trình song song

Tính toán số PI

Có rất nhiều thuật toán và cách thức tính số PI, ngày này với sự trợ giúp của máy tính người ta tính số PI với độ chính xác rất cao.

  • Giá trị số  PI có thể được tính theo nhiều cách khác nhau. Xem xét phương pháp tính số PI sau:
    1. Một hình tròn bán kính r nội tiếp trong hình vuông
    2. Gieo ngẫu nhiên các điểm trong hình vuông
    3. Xác định số điểm rơi trong hình vuông và số điểm trong hình tròn
    4. Đặt r = số điểm rơi vào hình tròn / số điểm rơi vào hình vuông
    5. PI ~ 4 r
    6. Chú ý càng nhiều điểm gieo thì độ chính xác càng caopi1
  • Thuật giải tuần tự:

npoints = 10000
circle_count = 0

do j = 1,npoints
  phát sinh 2 số ngẫu nhiên trong khoảng 0 và 1
  xcoordinate = random1
  ycoordinate = random2
  if (xcoordinate, ycoordinate) bên trong hình tròn 
  then circle_count = circle_count + 1
end do

PI = 4.0*circle_count/npoints
  • Chiến thuật song song: ngắt vòng lặp thành các phần mà có thể được thực thi bởi các tác vụ khác nhau
  • Thuật giải song song

npoints = 10000
circle_count = 0

p = số tác vụ
num = npoints/p

kiểm tra xem if tôi là MASTER hay WORKER 

do j = 1,num 
  phát sinh 2 số ngẫu nhiên trong khoảng 0 và 1
  xcoordinate = random1
  ycoordinate = random2
  if (xcoordinate, ycoordinate) bên trong hình tròn 
  then circle_count = circle_count + 1
end do

if Tôi là MASTER
  nhận về từ các WORKER số circle_count
  tính toán PI (sử dụng MASTER và các tính toán của WORKER)
else if tôi là WORKER
  gửi tới MASTER circle_count
endif

Xem thêm chi tiết các thủ tục, các hàm

Phan3_MPI

Có thể bạn sẽ thích…

1 phản hồi

  1. 16/07/2016

    […] Sử dụng thư viên MPI và các ví dụ minh hoạ […]

Để lại một bình luận