2012-12-07 31 views
0

我試圖實現一個MPI程序,以迭代方式將數組中的每個元素設置爲其本身及其鄰居(在前一個時間步中)的平均值,同時保持第一個元素和最後一個元素相同。對於一個過程,這工作正常;然而,對於多進程,我沒有得到正確的答案,特別是,第一個數組元素總是被覆蓋。爲什麼這個數組的第一個成員被MPI_Recv覆蓋?

我的初始化步驟似乎工作正常,至少就「計算前」輸出而言,無論所用進程的數量是1還是更多,打印相同的向量。

我不完全確定的一件事是我是否正確使用MPI_Request和MPI_Status;要注意的變量是sendL,sendRstatus

我試圖只包括代碼的相關部分; 「...」表示缺少某些東西。其中一些省略號有解釋被刪除的內容的意見。並行和單進程實現都是爲了比較而給出的。

... 
#include "mpi.h" 

... //definition of f() for initialization 

int main(int argc, char **argv) { 
    int   id, p, i, j, k, n, t, m, v, vp, 
       lbound, ubound, block_size, offset; 
    double  startwtime, endwtime; 
    float  time; 
    MPI_Request *sendL, *sendR; 
    MPI_Status *status; /* return status for receive */ 
    double  *prev, *cur, *temp; 

    ... // initialize MPI; get PE rank and size 

    .... // set the following: 
     // n = vector length, m = num iterations, k = buffer size 
     // v = verbose (true/false) 

    // Memory allocation for output from MPI functions 
    // Note that I never actually initialized these. Is this a problem? 
    sendL = (MPI_Request *) malloc(sizeof(MPI_Request)); 
    sendR = (MPI_Request *) malloc(sizeof(MPI_Request)); 
    status = (MPI_Status *) malloc(sizeof(MPI_Status)); 
    // Memory allocation for data array. 
    block_size = (n/p+2*k); 
    prev = (double *) malloc(sizeof(double) * block_size); 
    cur = (double *) malloc(sizeof(double) * block_size); 

    ... //malloc error handling 

    t = 0; 
    /* The following block is for a single process. It works correctly. */ 
    if(p==1){ 
    // Initialization 
    startwtime = MPI_Wtime(); 
    for(i=0;i<n;i++) prev[i] = f(i,n); 
    cur[0] = f(0,n); cur[n-1] = f(n-1,n); 
    if(v){ 
     printf("Before calculation\n"); 
     for(i=0;i<n;i++) printf("%f ",prev[i]); 
     printf("\n"); 
    } 
    while (t < m) { 
     for (i=1 ; i < n-1 ; i++) { 
      cur[i] = (prev[i-1]+prev[i]+prev[i+1])/3; 
     } 
     temp = prev; prev = cur; cur = temp; t++; 
     } 
    if(v){ 
     printf("After calculation:\n"); 
     for(i=0;i<n;i++) printf("%f ",prev[i]); 
     printf("\n"); 
    } 
    endwtime = MPI_Wtime(); 
    time = endwtime-startwtime; 
    printf("Sequential process complete, time: %f\n", time); 
    return MPI_Finalize();  
    } 
    /* Here is my parallel implementation. It has problems. */ 
    else{ 
    if (id == 0){ 
     startwtime = MPI_Wtime(); 
    } 
    // Initialization 
    offset = id*(n/p)-k; 
    for(i=0;i<block_size;i++) prev[i] = f(i+offset,n); 
    cur[0] = f(0,n); cur[block_size-1] = prev[block_size-1]; 
    if (id == 0){ 
     for (i=0;i<k;i++){ 
      prev[i] = f(0,n); 
      cur[i] = prev[i]; 
     } 
    } 
    if (id == p-1){ 
     for (i=block_size-k;i<block_size;i++){ 
      prev[i] = f(n-1,n); 
      cur[i] = prev[i]; 
     } 
    } 
    if(v && id == 0){ 
     printf("Before calculation:\n"); 
     for(j=k;j<(n/p)+k;j++) printf("%f ",prev[j]); 
     for(i=1;i<p;i++){ 
     MPI_Recv(prev+k,(n/p),MPI_DOUBLE_PRECISION,i,2,MPI_COMM_WORLD,status); 
     for(j=k;j<(n/p)+k;j++) printf("%f ",prev[j]); 
     } 
     printf("\n"); 
    } 
    else if (v){ 
     MPI_Isend(prev+k,(n/p),MPI_DOUBLE_PRECISION,0,2,MPI_COMM_WORLD,sendL); 
    } 
    lbound = (id == 0) ? (k+1) : (1); 
    ubound = (id == p-1) ? (block_size-k-2) : (block_size-2); 
    while (t < m) { 
     for (i=lbound ; i < ubound ; i++) { 
       cur[i] = (prev[i-1]+prev[i]+prev[i+1])/3; 
     } 
     temp = prev; prev = cur; cur = temp; t++; 
     if (t%k == 0){ 
      if (id > 0){ 
      // send to left 
      MPI_Isend(prev+k,k,MPI_DOUBLE_PRECISION,id-1,0,MPI_COMM_WORLD,sendL); 
      } 
      if (id < p-1) { 
      // send to right 
      MPI_Isend(prev+block_size-2*k,k, 
        MPI_DOUBLE_PRECISION,id+1,1,MPI_COMM_WORLD,sendR); 
      } 
      if (id < p-1){ 
      // receive from right 
      MPI_Recv(prev+block_size-k,k, 
        MPI_DOUBLE_PRECISION,id+1,0,MPI_COMM_WORLD,status); 
      } 
      if (id > 0) { 
      // receive from left 
      MPI_Recv(prev,k,MPI_DOUBLE_PRECISION,id-1,1,MPI_COMM_WORLD,status); 
      } 
     } 
     } 
    if(v && id == 0){ 
     printf("After calculation\n"); 
     for(j=k;j<(n/p)+k;j++) printf("%f ",prev[j]); 
     for(i=1;i<p;i++){ 
     MPI_Recv(prev+k,(n/p),MPI_DOUBLE_PRECISION,i,2,MPI_COMM_WORLD,status); 
     for(j=k;j<(n/p)+k;j++) printf("%f ",prev[j]); 
     } 
     printf("\n"); 
    } 
    else if (v){ 
     MPI_Isend(prev+k,(n/p),MPI_DOUBLE_PRECISION,0,2,MPI_COMM_WORLD,sendL); 
    } 
    if (id == 0){ 
     endwtime = MPI_Wtime(); 
     time = endwtime-startwtime; 
     printf("Process 0 complete, time: %f\n", time); 
    } 
    return MPI_Finalize();  
    } 
} 

回答

0

「計算前」輸出將覆蓋指針prev。哎呀。

2

第一件事是第一件事。這部分代碼過於複雜:

MPI_Request *sendL, *sendR; 
MPI_Status *status; /* return status for receive */ 

sendL = (MPI_Request *) malloc(sizeof(MPI_Request)); 
sendR = (MPI_Request *) malloc(sizeof(MPI_Request)); 
status = (MPI_Status *) malloc(sizeof(MPI_Status)); 

MPI中的句柄是簡單的類型,如整數或指針。動態分配在這種情況下沒有意義。狀態也是一個有3-4個字段的簡單結構,將它分配在堆上是沒有意義的。使用堆棧變量來代替:

MPI_Request sendL, sendR; 
MPI_Status status; 

還有另一個問題:你發起非阻塞發送,但從來沒有保證他們完成,即你永遠不會呼籲請求處理MPI_WaitMPI_Test。他們可能永遠不會真正進展到完成,這可能會導致接收代碼中的死鎖。實際上,您根本不需要這些非阻塞呼叫,而是使用MPI_Sendrecv,這是專門爲您使用MPI_Isend/MPI_Recv的組合而設計的。以下代碼:

if (id > 0){ 
    // send to left 
    MPI_Isend(prev+k,k,MPI_DOUBLE_PRECISION,id-1,0,MPI_COMM_WORLD,sendL); 
} 
if (id < p-1) { 
    // send to right 
    MPI_Isend(prev+block_size-2*k,k, 
      MPI_DOUBLE_PRECISION,id+1,1,MPI_COMM_WORLD,sendR); 
} 
if (id < p-1){ 
    // receive from right 
    MPI_Recv(prev+block_size-k,k, 
      MPI_DOUBLE_PRECISION,id+1,0,MPI_COMM_WORLD,status); 
} 
if (id > 0) { 
    // receive from left 
    MPI_Recv(prev,k,MPI_DOUBLE_PRECISION,id-1,1,MPI_COMM_WORLD,status); 
} 

可以被代替:

int prev_rank, next_rank; 

prev_rank = (id > 0) ? id-1 : MPI_PROC_NULL; 
next_rank = (id < p-1) ? id+1 : MPI_PROC_NULL; 

... 

MPI_Sendrecv(prev+k, k, MPI_DOUBLE, prev_rank, 0, 
      prev+block_size-k, k, MPI_DOUBLE, next_rank, 0, MPI_COMM_WORLD, &status); 
MPI_Sendrecv(prev+block_size-2*k, k, MPI_DOUBLE, next_rank, 1, 
      prev, k, MPI_DOUBLE, prev_rank, 1, MPI_COMM_WORLD, &status); 

使用空處理的概念除去等級檢查,是與秩MPI_PROC_NULL的處理。這是MPI中一個非常特殊的級別 - 您可以始終發送和接收消息,而這些操作完全沒有任何操作。請注意,正確的MPI數據類型是MPI_DOUBLEMPI_DOUBLE_PRECISION適用於Fortran數據類型DOUBLE PRECISION。因爲MPI_Sendrecv是一個阻塞調用,所以每次調用都被寫入以便在接收來自前一個進程的數據時將數據發送到下一個進程,以防止死鎖。

+0

非常感謝您的幫助,特別是對於我不知道的MPI_Sendrecv()函數。正如我在我的回答中所說的,基本問題是在代碼中覆蓋初始值以打印出初始數組,但它也不會損害清理代碼。 –

+0

另外,你確定這會造成死鎖嗎?確實,我從來沒有明確地等待任何Isend()調用,但是由於Recv()調用**是**阻塞的,在我看來,循環不可能發生;並且由於等級0最終等待所有其他等級,所有過程在輸出之前完成。 –

+0

在這種情況下 - 不,由於雙方的空位(與他們的溝通總是立即成功,即使給予阻止呼叫)。但是可以有周期性的邊界條件。除此之外,它需要2個「時間步驟」來完成交換。如果在兩個發送接收中交換接收部分,它將採用'2 * p'「時間步長」。 (這也不是你的問題的答案,因爲你已經解決了覆蓋問題。) –

相關問題