2012-02-13 12 views
43

Làm thế nào để bạn gửi các khối mảng 2-D tới các bộ vi xử lý khác nhau? Giả sử kích thước mảng 2D là 400x400, tôi muốn gửi các khối kích thước 100X100 đến các bộ vi xử lý khác nhau. Ý tưởng là mỗi bộ xử lý sẽ thực hiện tính toán trên khối riêng biệt của nó và gửi kết quả của nó trở lại bộ xử lý đầu tiên cho kết quả cuối cùng.
Tôi đang sử dụng MPI trong các chương trình C.gửi các khối của mảng 2D trong C bằng cách sử dụng MPI

+2

Bạn nên chấp nhận câu trả lời ... – gsamaras

Trả lời

106

Hãy để tôi bắt đầu bằng cách nói rằng bạn thường không thực sự muốn làm điều này - phân tán và thu thập các khối dữ liệu khổng lồ từ một số quy trình "chính". Thông thường, bạn muốn mỗi công việc được chugging đi vào mảnh ghép của riêng nó, và bạn nên nhắm đến việc không bao giờ có một bộ xử lý cần một "cái nhìn tổng thể" của toàn bộ dữ liệu; ngay sau khi bạn yêu cầu điều đó, bạn giới hạn khả năng mở rộng và kích thước vấn đề. Nếu bạn đang làm điều này cho I/O - một quá trình đọc dữ liệu, sau đó phân tán nó, sau đó tập hợp nó trở lại để viết, bạn sẽ muốn cuối cùng để nhìn vào MPI-IO.

Nhận câu hỏi của bạn, tuy nhiên, Bộ KH & ĐT có các cách rất hay để kéo dữ liệu tùy ý ra khỏi bộ nhớ, và phân tán/thu thập dữ liệu đó đến và từ một bộ xử lý. Thật không may là đòi hỏi một số lượng hợp lý các khái niệm MPI - Các loại MPI, phạm vi mở rộng và các hoạt động tập thể. Rất nhiều ý tưởng cơ bản được thảo luận trong câu trả lời cho câu hỏi này - MPI_Type_create_subarray and MPI_Gather.

Cập nhật - Trong ánh sáng lạnh trong ngày, đây là rất nhiều mã và không có nhiều lời giải thích. Vì vậy, hãy để tôi mở rộng một chút.

Hãy xem xét mảng số nguyên 1d toàn cục mà tác vụ 0 có mà bạn muốn phân phối cho một số nhiệm vụ MPI, để mỗi chúng có được một phần trong mảng cục bộ của chúng. Giả sử bạn có 4 tác vụ và mảng toàn cục là []. Bạn có thể có nhiệm vụ 0 gửi bốn tin nhắn (bao gồm cả một chính nó) để phân phối này, và khi đó là thời gian để tái lắp ráp, nhận được bốn tin nhắn để bó lại với nhau; nhưng điều đó rõ ràng là tốn rất nhiều thời gian với số lượng lớn các quy trình. Có các thói quen tối ưu hóa cho các loại hoạt động này - phân tán/thu thập các hoạt động. Vì vậy, trong trường hợp này 1d bạn muốn làm một cái gì đó như thế này:

int global[8]; /* only task 0 has this */ 
int local[2]; /* everyone has this */ 
const int root = 0; /* the processor with the initial global data */ 

if (rank == root) { 
    for (int i=0; i<7; i++) global[i] = i; 
} 

MPI_Scatter(global, 2, MPI_INT,  /* send everyone 2 ints from global */ 
      local, 2, MPI_INT,  /* each proc receives 2 ints into local */ 
      root, MPI_COMM_WORLD); /* sending process is root, all procs in */ 
            /* MPI_COMM_WORLD participate */ 

Sau đó, dữ liệu của các bộ vi xử lý sẽ như thế nào

task 0: local:[01] global: [] 
task 1: local:[23] global: [garbage-] 
task 2: local:[45] global: [garbage-] 
task 3: local:[67] global: [garbage-] 

Đó là, các hoạt động phân tán mất mảng toàn cầu và gửi tiếp giáp 2-int khối cho tất cả các bộ vi xử lý.

Để tái lắp ráp mảng, chúng tôi sử dụng các hoạt động MPI_Gather(), mà làm việc giống hệt nhau nhưng ngược lại:

for (int i=0; i<2; i++) 
    local[i] = local[i] + rank; 

MPI_Gather(local, 2, MPI_INT,  /* everyone sends 2 ints from local */ 
      global, 2, MPI_INT,  /* root receives 2 ints each proc into global */ 
      root, MPI_COMM_WORLD); /* recv'ing process is root, all procs in */ 
            /* MPI_COMM_WORLD participate */ 

và bây giờ các dữ liệu trông giống như

task 0: local:[01] global: [0134679a] 
task 1: local:[34] global: [garbage-] 
task 2: local:[67] global: [garbage-] 
task 3: local:[9a] global: [garbage-] 

Thu thập mang lại tất cả các dữ liệu trở lại và đây là 10 vì tôi không nghĩ định dạng của mình đủ cẩn thận khi bắt đầu ví dụ này.

Điều gì sẽ xảy ra nếu số điểm dữ liệu không phân chia đồng đều số lượng quy trình và chúng tôi cần gửi số lượng mặt hàng khác nhau cho từng quy trình? Sau đó, bạn cần một phiên bản phân tán tổng quát, MPI_Scatterv(), cho phép bạn chỉ định số lượng cho mỗi bộ xử lý và chuyển vị - trong mảng toàn cục mà phần dữ liệu bắt đầu. Vì vậy, giả sử bạn có một loạt các ký tự [abcdefghi] với 9 ký tự và bạn sẽ chỉ định mọi quá trình hai ký tự ngoại trừ phần tử cuối cùng, có ba ký tự.Sau đó, bạn sẽ cần

char global[9]; /* only task 0 has this */ 
char local[3]={'-','-','-'}; /* everyone has this */ 
int mynum;      /* how many items */ 
const int root = 0; /* the processor with the initial global data */ 

if (rank == 0) { 
    for (int i=0; i<8; i++) global[i] = 'a'+i; 
} 

int counts[4] = {2,2,2,3}; /* how many pieces of data everyone has */ 
mynum = counts[rank]; 
int displs[4] = {0,2,4,6}; /* the starting point of everyone's data */ 
          /* in the global array */ 

MPI_Scatterv(global, counts, displs, /* proc i gets counts[i] pts from displs[i] */ 
      MPI_INT,  
      local, mynum, MPI_INT; /* I'm receiving mynum MPI_INTs into local */ 
      root, MPI_COMM_WORLD); 

Bây giờ các dữ liệu trông giống như

task 0: local:[ab-] global: [abcdefghi] 
task 1: local:[cd-] global: [garbage--] 
task 2: local:[ef-] global: [garbage--] 
task 3: local:[ghi] global: [garbage--] 

Bây giờ bạn đã sử dụng scatterv để phân phối các khoản đột xuất dữ liệu. Sự dịch chuyển trong mỗi trường hợp là hai * hạng (được đo bằng ký tự; chuyển vị là đơn vị của các loại được gửi cho một phân tán hoặc nhận được cho một tập hợp; nó không phải là byte hoặc cái gì đó) từ đầu mảng, và số lượng là {2,2,2,3}. Nếu nó là bộ vi xử lý đầu tiên chúng tôi muốn có 3 ký tự, chúng tôi sẽ đặt số đếm = {3,2,2,2} và chuyển vị sẽ là {0,3,5,7}. Gatherv một lần nữa hoạt động chính xác như nhau nhưng ngược lại; các mảng đếm và chuyển sẽ vẫn giữ nguyên.

Bây giờ, đối với 2D, điều này hơi phức tạp hơn một chút. Nếu chúng ta muốn gửi các sublocks 2d của một mảng 2d, dữ liệu chúng ta đang gửi bây giờ không còn tiếp giáp nữa. Nếu chúng tôi đưa (nói) subblocks 3x3 của một mảng 6x6 đến 4 bộ vi xử lý, dữ liệu chúng tôi đang gửi có lỗ hổng trong nó:

2D Array 

    --------- 
    |000|111| 
    |000|111| 
    |000|111| 
    |---+---| 
    |222|333| 
    |222|333| 
    |222|333| 
    --------- 

Actual layout in memory 

    [000111000111000111222333222333222333] 

(Lưu ý rằng tất cả các máy tính hiệu suất cao đi xuống đến tìm hiểu cách bố trí Nếu chúng tôi muốn gửi dữ liệu được đánh dấu "1" đến nhiệm vụ 1, chúng tôi cần bỏ qua ba giá trị, gửi ba giá trị, bỏ qua ba giá trị, gửi ba giá trị, bỏ qua ba giá trị, gửi ba giá trị. Một biến chứng thứ hai là nơi các tiểu vùng dừng lại và bắt đầu; lưu ý rằng vùng "1" không bắt đầu khi vùng "0" dừng; sau phần tử cuối cùng của vùng "0", vị trí tiếp theo trong bộ nhớ là một phần theo cách thông qua vùng "1".

Trước tiên, hãy giải quyết vấn đề bố cục đầu tiên - cách chỉ lấy dữ liệu chúng tôi muốn gửi. Chúng tôi luôn có thể sao chép tất cả dữ liệu vùng "0" sang vùng khác, mảng liền kề và gửi đi; nếu chúng tôi lên kế hoạch đủ cẩn thận, chúng tôi thậm chí có thể làm điều đó theo cách mà chúng tôi có thể gọi MPI_Scatter về kết quả. Nhưng chúng tôi không muốn phải chuyển đổi toàn bộ cấu trúc dữ liệu chính theo cách đó.

Cho đến nay, tất cả các loại dữ liệu MPI mà chúng tôi đã sử dụng là những loại đơn giản - MPI_INT chỉ định (nói) 4 byte liên tiếp. Tuy nhiên, MPI cho phép bạn tạo các kiểu dữ liệu riêng của mình mô tả các bố cục dữ liệu phức tạp tùy ý trong bộ nhớ. Và trường hợp này - hình chữ nhật tiểu vùng của một mảng - là đủ phổ biến mà có một cuộc gọi cụ thể cho điều đó. Đối với trường hợp 2 chiều chúng tôi mô tả ở trên,

MPI_Datatype newtype; 
    int sizes[2] = {6,6}; /* size of global array */ 
    int subsizes[2] = {3,3}; /* size of sub-region */ 
    int starts[2] = {0,0}; /* let's say we're looking at region "0", 
           which begins at index [0,0] */ 

    MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_INT, &newtype); 
    MPI_Type_commit(&newtype); 

Điều này tạo ra một loại mà chọn ra chỉ là khu vực "0" từ mảng toàn cầu; chúng ta có thể gửi chỉ là phần dữ liệu nay đến một bộ xử lý

MPI_Send(&(global[0][0]), 1, newtype, dest, tag, MPI_COMM_WORLD); /* region "0" */ 

và quá trình tiếp nhận có thể nhận được nó vào một mảng địa phương. Lưu ý rằng quá trình nhận, nếu nó chỉ nhận nó vào một mảng 3x3, có thể không mô tả những gì nó nhận được dưới dạng một loại newtype; không còn mô tả bố cục bộ nhớ.Thay vào đó, nó chỉ nhận được một khối 3 * 3 = 9 số nguyên:

MPI_Recv(&(local[0][0]), 3*3, MPI_INT, 0, tag, MPI_COMM_WORLD); 

Lưu ý rằng chúng ta có thể làm điều này cho các tiểu vùng khác nữa, hoặc bằng cách tạo ra một loại khác nhau (với start mảng khác nhau) cho các khối khác hoặc chỉ bằng cách gửi tại điểm bắt đầu của khối cụ thể:

MPI_Send(&(global[0][3]), 1, newtype, dest, tag, MPI_COMM_WORLD); /* region "1" */ 
    MPI_Send(&(global[3][0]), 1, newtype, dest, tag, MPI_COMM_WORLD); /* region "2" */ 
    MPI_Send(&(global[3][3]), 1, newtype, dest, tag, MPI_COMM_WORLD); /* region "3" */ 

Cuối cùng, lưu ý rằng chúng tôi yêu cầu toàn bộ và cục bộ là khối liên tiếp của bộ nhớ tại đây; nghĩa là, &(global[0][0])&(local[0][0]) (hoặc, tương đương, *global*local trỏ tới các khối bộ nhớ liền kề 6 * 6 và 3 * 3; không được bảo đảm theo cách phân bổ mảng đa năng động thông thường. Dưới đây là chúng tôi hiểu cách chỉ định các tiểu vùng, chỉ còn một điều nữa cần thảo luận trước khi sử dụng các hoạt động phân tán/thu thập và đó là "kích thước" của các loại này. Chúng tôi không thể sử dụng MPI_Scatter() (hoặc thậm chí là scatterv) với các kiểu này, bởi vì các kiểu này có 16 số nguyên, có nghĩa là, khi chúng kết thúc là 16 số nguyên sau khi chúng bắt đầu - và nơi chúng kết thúc không phù hợp với vị trí khối tiếp theo bắt đầu, vì vậy chúng tôi không thể chỉ sử dụng phân tán - nó sẽ chọn sai vị trí để bắt đầu gửi dữ liệu đến bộ vi xử lý tiếp theo. Tất nhiên, chúng ta có thể sử dụng MPI_Scatterv() và chỉ định chính mình, và đó là những gì chúng ta sẽ làm - ngoại trừ sự dịch chuyển nằm trong các đơn vị kích thước gửi-loại, và điều đó cũng không giúp chúng ta; các khối bắt đầu tại các số nguyên của (0,3,18,21) số nguyên từ đầu của mảng toàn cầu, và thực tế là một khối kết thúc 16 số nguyên từ nơi nó bắt đầu không cho phép chúng tôi thể hiện những chuyển vị trong bội số nguyên ở tất cả .

Để giải quyết vấn đề này, MPI cho phép bạn đặt mức độ của loại cho mục đích của các tính toán này. Nó không cắt ngắn loại; nó chỉ được sử dụng để tìm ra nơi yếu tố tiếp theo bắt đầu cho phần tử cuối cùng. Đối với các loại như thế này với các lỗ hổng trong chúng, thường rất thuận tiện để thiết lập mức độ nhỏ hơn khoảng cách trong bộ nhớ đến cuối thực tế của loại.

Chúng tôi có thể đặt mức độ là bất kỳ điều gì thuận tiện cho chúng tôi. Chúng tôi chỉ có thể làm cho 1 số nguyên, và sau đó thiết lập chuyển vị trong đơn vị của số nguyên. Trong trường hợp này, mặc dù, tôi muốn đặt mức độ là 3 số nguyên - kích thước của một hàng phụ - theo cách đó, khối "1" bắt đầu ngay lập tức sau khi chặn "0" và chặn "3" bắt đầu ngay lập tức sau khi chặn " 2 ". Thật không may, nó không hoàn toàn hoạt động như độc đáo khi nhảy từ khối "2" để chặn "3", nhưng điều đó không thể tránh được.

Vì vậy, để làm tan tác dân subblocks trong trường hợp này, chúng tôi muốn làm như sau:

MPI_Datatype type, resizedtype; 
    int sizes[2] = {6,6}; /* size of global array */ 
    int subsizes[2] = {3,3}; /* size of sub-region */ 
    int starts[2] = {0,0}; /* let's say we're looking at region "0", 
           which begins at index [0,0] */ 

    /* as before */ 
    MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_INT, &type); 
    /* change the extent of the type */ 
    MPI_Type_create_resized(type, 0, 3*sizeof(int), &resizedtype); 
    MPI_Type_commit(&resizedtype); 

Dưới đây chúng tôi đã tạo kiểu khối giống như trước, nhưng chúng tôi đã thay đổi kích cỡ nó; chúng tôi đã không thay đổi nơi mà các loại "bắt đầu" (0) nhưng chúng tôi đã thay đổi nơi nó "kết thúc" (3 ints). Chúng tôi đã không đề cập đến điều này trước đây, nhưng cần có MPI_Type_commit để có thể sử dụng loại; nhưng bạn chỉ cần cam kết loại cuối cùng bạn thực sự sử dụng, không phải bất kỳ bước trung gian nào. Bạn sử dụng MPI_Type_free để giải phóng loại khi bạn hoàn tất.

Vì vậy, bây giờ, cuối cùng, chúng ta có thể scatterv các khối: các thao tác dữ liệu trên là một chút phức tạp, nhưng khi nó đã được thực hiện, scatterv trông giống như trước đây:

int counts[4] = {1,1,1,1}; /* how many pieces of data everyone has, in units of blocks */ 
int displs[4] = {0,1,6,7}; /* the starting point of everyone's data */ 
          /* in the global array, in block extents */ 

MPI_Scatterv(global, counts, displs, /* proc i gets counts[i] types from displs[i] */ 
      resizedtype,  
      local, 3*3, MPI_INT; /* I'm receiving 3*3 MPI_INTs into local */ 
      root, MPI_COMM_WORLD); 

Và bây giờ chúng tôi đang thực hiện, sau một chuyến tham quan nhỏ, phân tán và các loại có nguồn gốc từ MPI.

Mã ví dụ hiển thị cả thao tác thu thập và hoạt động phân tán, với mảng ký tự, theo sau. Chạy chương trình:

$ mpirun -n 4 ./gathervarray 
Global array is: 

3456789012 
6789
9
2345678901 
56789
89
1234567890 
456789
789
Local process on rank 0 is: 
|| 
|34567| 
|67890| 
|90123| 
|23456| 
Local process on rank 1 is: 
|56789| 
|89012| 
|12345| 
|45678| 
|78901| 
Local process on rank 2 is: 
|56789| 
|89012| 
|12345| 
|45678| 
|78901| 
Local process on rank 3 is: 
|| 
|34567| 
|67890| 
|90123| 
|23456| 
Processed grid: 
AAAAABBBBB 
AAAAABBBBB 
AAAAABBBBB 
AAAAABBBBB 
AAAAABBBBB 
CCCCCDDDDD 
CCCCCDDDDD 
CCCCCDDDDD 
CCCCCDDDDD 
CCCCCDDDDD 

và mã sau.

#include <stdio.h> 
#include <math.h> 
#include <stdlib.h> 
#include "mpi.h" 

int malloc2dchar(char ***array, int n, int m) { 

    /* allocate the n*m contiguous items */ 
    char *p = (char *)malloc(n*m*sizeof(char)); 
    if (!p) return -1; 

    /* allocate the row pointers into the memory */ 
    (*array) = (char **)malloc(n*sizeof(char*)); 
    if (!(*array)) { 
     free(p); 
     return -1; 
    } 

    /* set up the pointers into the contiguous memory */ 
    for (int i=0; i<n; i++) 
     (*array)[i] = &(p[i*m]); 

    return 0; 
} 

int free2dchar(char ***array) { 
    /* free the memory - the first element of the array is at the start */ 
    free(&((*array)[0][0])); 

    /* free the pointers into the memory */ 
    free(*array); 

    return 0; 
} 

int main(int argc, char **argv) { 
    char **global, **local; 
    const int gridsize=10; // size of grid 
    const int procgridsize=2; // size of process grid 
    int rank, size;  // rank of current process and no. of processes 

    MPI_Init(&argc, &argv); 
    MPI_Comm_size(MPI_COMM_WORLD, &size); 
    MPI_Comm_rank(MPI_COMM_WORLD, &rank); 


    if (size != procgridsize*procgridsize) { 
     fprintf(stderr,"%s: Only works with np=%d for now\n", argv[0], procgridsize); 
     MPI_Abort(MPI_COMM_WORLD,1); 
    } 


    if (rank == 0) { 
     /* fill in the array, and print it */ 
     malloc2dchar(&global, gridsize, gridsize); 
     for (int i=0; i<gridsize; i++) { 
      for (int j=0; j<gridsize; j++) 
       global[i][j] = '0'+(3*i+j)%10; 
     } 


     printf("Global array is:\n"); 
     for (int i=0; i<gridsize; i++) { 
      for (int j=0; j<gridsize; j++) 
       putchar(global[i][j]); 

      printf("\n"); 
     } 
    } 

    /* create the local array which we'll process */ 
    malloc2dchar(&local, gridsize/procgridsize, gridsize/procgridsize); 

    /* create a datatype to describe the subarrays of the global array */ 

    int sizes[2] = {gridsize, gridsize};   /* global size */ 
    int subsizes[2] = {gridsize/procgridsize, gridsize/procgridsize};  /* local size */ 
    int starts[2] = {0,0};      /* where this one starts */ 
    MPI_Datatype type, subarrtype; 
    MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_CHAR, &type); 
    MPI_Type_create_resized(type, 0, gridsize/procgridsize*sizeof(char), &subarrtype); 
    MPI_Type_commit(&subarrtype); 

    char *globalptr=NULL; 
    if (rank == 0) globalptr = &(global[0][0]); 

    /* scatter the array to all processors */ 
    int sendcounts[procgridsize*procgridsize]; 
    int displs[procgridsize*procgridsize]; 

    if (rank == 0) { 
     for (int i=0; i<procgridsize*procgridsize; i++) sendcounts[i] = 1; 
     int disp = 0; 
     for (int i=0; i<procgridsize; i++) { 
      for (int j=0; j<procgridsize; j++) { 
       displs[i*procgridsize+j] = disp; 
       disp += 1; 
      } 
      disp += ((gridsize/procgridsize)-1)*procgridsize; 
     } 
    } 


    MPI_Scatterv(globalptr, sendcounts, displs, subarrtype, &(local[0][0]), 
       gridsize*gridsize/(procgridsize*procgridsize), MPI_CHAR, 
       0, MPI_COMM_WORLD); 

    /* now all processors print their local data: */ 

    for (int p=0; p<size; p++) { 
     if (rank == p) { 
      printf("Local process on rank %d is:\n", rank); 
      for (int i=0; i<gridsize/procgridsize; i++) { 
       putchar('|'); 
       for (int j=0; j<gridsize/procgridsize; j++) { 
        putchar(local[i][j]); 
       } 
       printf("|\n"); 
      } 
     } 
     MPI_Barrier(MPI_COMM_WORLD); 
    } 

    /* now each processor has its local array, and can process it */ 
    for (int i=0; i<gridsize/procgridsize; i++) { 
     for (int j=0; j<gridsize/procgridsize; j++) { 
      local[i][j] = 'A' + rank; 
     } 
    } 

    /* it all goes back to process 0 */ 
    MPI_Gatherv(&(local[0][0]), gridsize*gridsize/(procgridsize*procgridsize), MPI_CHAR, 
       globalptr, sendcounts, displs, subarrtype, 
       0, MPI_COMM_WORLD); 

    /* don't need the local data anymore */ 
    free2dchar(&local); 

    /* or the MPI data type */ 
    MPI_Type_free(&subarrtype); 

    if (rank == 0) { 
     printf("Processed grid:\n"); 
     for (int i=0; i<gridsize; i++) { 
      for (int j=0; j<gridsize; j++) { 
       putchar(global[i][j]); 
      } 
      printf("\n"); 
     } 

     free2dchar(&global); 
    } 


    MPI_Finalize(); 

    return 0; 
} 
+6

này đi lên trong một số phiên bản một lần nữa và một lần nữa ở đây; Tôi hy vọng sẽ viết một câu trả lời, chúng tôi chỉ có thể tiếp tục chỉ cho mọi người. Nhưng cảm ơn :) –

+0

Tôi khá thành thạo với Fortran MPI, nhưng thích điều này để tham khảo trong tương lai. Ngoài ra, tôi nhận xét của con thứ hai. – milancurcic

+0

Toàn bộ quá trình này dễ dàng hơn ở Fortran, trong đó có các mảng đa chiều được xây dựng trong ngôn ngữ; một cái gì đó mà C tiếp tục chọn không bao gồm. Và cả hai bạn đã đưa ra câu trả lời khá mạnh về SO ... –

1

Tôi thấy dễ dàng hơn khi kiểm tra theo cách đó.

#include <stdio.h> 
#include <math.h> 
#include <stdlib.h> 
#include "mpi.h" 

/* 
This is a version with integers, rather than char arrays, presented in this 
very good answer: http://stackoverflow.com/a/9271753/2411320 
It will initialize the 2D array, scatter it, increase every value by 1 and then gather it back. 
*/ 

int malloc2D(int ***array, int n, int m) { 
    int i; 
    /* allocate the n*m contiguous items */ 
    int *p = malloc(n*m*sizeof(int)); 
    if (!p) return -1; 

    /* allocate the row pointers into the memory */ 
    (*array) = malloc(n*sizeof(int*)); 
    if (!(*array)) { 
     free(p); 
     return -1; 
    } 

    /* set up the pointers into the contiguous memory */ 
    for (i=0; i<n; i++) 
     (*array)[i] = &(p[i*m]); 

    return 0; 
} 

int free2D(int ***array) { 
    /* free the memory - the first element of the array is at the start */ 
    free(&((*array)[0][0])); 

    /* free the pointers into the memory */ 
    free(*array); 

    return 0; 
} 

int main(int argc, char **argv) { 
    int **global, **local; 
    const int gridsize=4; // size of grid 
    const int procgridsize=2; // size of process grid 
    int rank, size;  // rank of current process and no. of processes 
    int i, j, p; 

    MPI_Init(&argc, &argv); 
    MPI_Comm_size(MPI_COMM_WORLD, &size); 
    MPI_Comm_rank(MPI_COMM_WORLD, &rank); 


    if (size != procgridsize*procgridsize) { 
     fprintf(stderr,"%s: Only works with np=%d for now\n", argv[0], procgridsize); 
     MPI_Abort(MPI_COMM_WORLD,1); 
    } 


    if (rank == 0) { 
     /* fill in the array, and print it */ 
     malloc2D(&global, gridsize, gridsize); 
     int counter = 0; 
     for (i=0; i<gridsize; i++) { 
      for (j=0; j<gridsize; j++) 
       global[i][j] = ++counter; 
     } 


     printf("Global array is:\n"); 
     for (i=0; i<gridsize; i++) { 
      for (j=0; j<gridsize; j++) { 
       printf("%2d ", global[i][j]); 
      } 
      printf("\n"); 
     } 
    } 
    //return; 

    /* create the local array which we'll process */ 
    malloc2D(&local, gridsize/procgridsize, gridsize/procgridsize); 

    /* create a datatype to describe the subarrays of the global array */ 
    int sizes[2] = {gridsize, gridsize};   /* global size */ 
    int subsizes[2] = {gridsize/procgridsize, gridsize/procgridsize};  /* local size */ 
    int starts[2] = {0,0};      /* where this one starts */ 
    MPI_Datatype type, subarrtype; 
    MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, MPI_INT, &type); 
    MPI_Type_create_resized(type, 0, gridsize/procgridsize*sizeof(int), &subarrtype); 
    MPI_Type_commit(&subarrtype); 

    int *globalptr=NULL; 
    if (rank == 0) 
     globalptr = &(global[0][0]); 

    /* scatter the array to all processors */ 
    int sendcounts[procgridsize*procgridsize]; 
    int displs[procgridsize*procgridsize]; 

    if (rank == 0) { 
     for (i=0; i<procgridsize*procgridsize; i++) 
      sendcounts[i] = 1; 
     int disp = 0; 
     for (i=0; i<procgridsize; i++) { 
      for (j=0; j<procgridsize; j++) { 
       displs[i*procgridsize+j] = disp; 
       disp += 1; 
      } 
      disp += ((gridsize/procgridsize)-1)*procgridsize; 
     } 
    } 


    MPI_Scatterv(globalptr, sendcounts, displs, subarrtype, &(local[0][0]), 
       gridsize*gridsize/(procgridsize*procgridsize), MPI_INT, 
       0, MPI_COMM_WORLD); 

    /* now all processors print their local data: */ 

    for (p=0; p<size; p++) { 
     if (rank == p) { 
      printf("Local process on rank %d is:\n", rank); 
      for (i=0; i<gridsize/procgridsize; i++) { 
       putchar('|'); 
       for (j=0; j<gridsize/procgridsize; j++) { 
        printf("%2d ", local[i][j]); 
       } 
       printf("|\n"); 
      } 
     } 
     MPI_Barrier(MPI_COMM_WORLD); 
    } 

    /* now each processor has its local array, and can process it */ 
    for (i=0; i<gridsize/procgridsize; i++) { 
     for (j=0; j<gridsize/procgridsize; j++) { 
      local[i][j] += 1; // increase by one the value 
     } 
    } 

    /* it all goes back to process 0 */ 
    MPI_Gatherv(&(local[0][0]), gridsize*gridsize/(procgridsize*procgridsize), MPI_INT, 
       globalptr, sendcounts, displs, subarrtype, 
       0, MPI_COMM_WORLD); 

    /* don't need the local data anymore */ 
    free2D(&local); 

    /* or the MPI data type */ 
    MPI_Type_free(&subarrtype); 

    if (rank == 0) { 
     printf("Processed grid:\n"); 
     for (i=0; i<gridsize; i++) { 
      for (j=0; j<gridsize; j++) { 
       printf("%2d ", global[i][j]); 
      } 
      printf("\n"); 
     } 

     free2D(&global); 
    } 


    MPI_Finalize(); 

    return 0; 
} 

Output:

linux16:>mpicc -o main main.c 
linux16:>mpiexec -n 4 main Global array is: 
1 2 3 4 
5 6 7 8 
9 10 11 12 
13 14 15 16 
Local process on rank 0 is: 
| 1 2 | 
| 5 6 | 
Local process on rank 1 is: 
| 3 4 | 
| 7 8 | 
Local process on rank 2 is: 
| 9 10 | 
|13 14 | 
Local process on rank 3 is: 
|11 12 | 
|15 16 | 
Processed grid: 
2 3 4 5 
6 7 8 9 
10 11 12 13 
14 15 16 17 
Các vấn đề liên quan