2015-06-03 94 views
1

我正在使用的代碼使用MPI將大型3維陣列(立方體)分成沿着所有三個軸的子域以形成更小的立方體。我以前曾經在一個簡單的二維等價物上工作,沒有問題。MPI將阻塞轉換爲非阻塞問題

現在,由於MPI將MPI_SEND和MPI_RECV視爲非阻塞調用小塊數據的煩人習慣(或令人滿意的習慣,取決於您如何看待它),這種從2D到3D的遷移帶來了很多困難。所有對2D都非常好用的調用在3D中最輕微的挑釁時開始出現死鎖,因爲必須在進程之間傳遞的數據現在是3D數組,因此數據量更大。

經過一週的鬥爭並拉出大量頭髮後,構建了一組複雜的MPI_SEND和MPI_RECV調用,它們設法將數據傳遞到域中每個立方體的面,邊和角落(設置了週期性和非週期性設置適當地在不同的邊界)。幸福不會持久。在添加一個新的邊界條件後,該邊界條件需要域之間的單元格之間的額外通信路徑,代碼陷入了另一個死鎖的惡性循環。

已經夠了,我決定訴諸非阻塞電話。現在有了這麼多的背景,我希望我的意圖與下面的代碼將是很簡單的。我不包括我用來在子域的邊緣和角落之間傳輸數據的代碼。如果我能理清立方體表面之間的溝通,我將能夠把其他所有東西都整齊地落入到位。

該代碼使用五個陣列以簡化數據傳輸:

  1. RARR =陣列相鄰小區

  2. tsArr =標籤用於將數據發送到每個相鄰

  3. 陣列的行列的trArr =用於接收來自每個鄰居的數據的標籤陣列

  4. lsArr =限制數組(i ndices)來描述的數據塊要發送

  5. lrArr =陣列的限制(索引)來描述的數據塊將被接收

現在,因爲每個立方體具有6個鄰居共享一個面每個,RARR,tsArr和trArr各自是長度爲6的整數數組,從另一方面限陣列是如下所述的二維陣列:

lsArr = [[xStart, xEnd, yStart, yEnd, zStart, zEnd, dSize], !for face 1 sending 
     [xStart, xEnd, yStart, yEnd, zStart, zEnd, dSize], !for face 2 sending 
     . 
     . 
     [xStart, xEnd, yStart, yEnd, zStart, zEnd, dSize]] !for face 6 sending 

因此,一個呼叫跨越發送可變dCube的值ith一個細胞(過程)的表面會像bel一樣發生流:

call MPI_SEND(dCube(lsArr(i, 1):lsArr(i, 2), lsArr(i, 3):lsArr(i, 4), lsArr(i, 5):lsArr(i, 6)), lsArr(i, 7), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr) 

並與匹配目的地等級和標籤的另一過程將接收相同的塊如下:

call MPI_RECV(dCube(lrArr(i, 1):lrArr(i, 2), lrArr(i, 3):lrArr(i, 4), lrArr(i, 5):lrArr(i, 6)), lrArr(i, 7), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr) 

的lsArr以及源和目的地過程的lrArr進行測試,以顯示匹配的尺寸(但不同的限制)。標籤數組也被檢查以查看它們是否匹配。

現在我的早期版本的阻塞調用的代碼工作完美,因此我對上述數組中值的正確性有99%的自信。如果有理由懷疑他們的準確性,我可以添加這些細節,但是這個帖子將變爲非常長的

下面是我的代碼的阻塞版本,完美的工作。我很抱歉,如果它有點棘手。如果有必要進一步澄清問題,我會這樣做。

subroutine exchangeData(dCube) 
use someModule 

implicit none 
integer :: i, j 
double precision, intent(inout), dimension(xS:xE, yS:yE, zS:zE) :: dCube 

do j = 1, 3 
    if (mod(edArr(j), 2) == 0) then !!edArr = [xRank, yRank, zRank] 
     i = 2*j - 1 
     call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), & 
         lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr) 

     i = 2*j 
     call MPI_RECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), & 
         lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr) 
    else 
     i = 2*j 
     call MPI_RECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), & 
         lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr) 

     i = 2*j - 1 
     call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), & 
         lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr) 
    end if 

    if (mod(edArr(j), 2) == 0) then 
     i = 2*j 
     call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), & 
         lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr) 

     i = 2*j - 1 
     call MPI_RECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), & 
         lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr) 
    else 
     i = 2*j - 1 
     call MPI_RECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), & 
         lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, stVal, ierr) 

     i = 2*j 
     call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), & 
         lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr) 
    end if 
end do 
end subroutine exchangeData 

基本上它沿每個方向中,x,y和z進和第一發送從奇數編號數據面,然後偶數面。我不知道是否有更簡單的方法來做到這一點。這是經過無數的死鎖代碼,這幾乎讓我發瘋。通過邊緣和角落發送數據的代碼甚至更長。

現在是我現在遇到的實際問題。我取代了上面的代碼與下面的(有點天真,也許?)

subroutine exchangeData(dCube) 
use someModule 

implicit none 
integer :: i, j 
integer, dimension(6) :: fRqLst 
integer :: stLst(MPI_STATUS_SIZE, 6) 
double precision, intent(inout), dimension(xS:xE, yS:yE, zS:zE) :: dCube 

fRqLst = MPI_REQUEST_NULL 
do i = 1, 6 
    call MPI_IRECV(dCube(lrArr(1, i):lrArr(2, i), lrArr(3, i):lrArr(4, i), lrArr(5, i):lrArr(6, i)), & 
         lrArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), trArr(i), MPI_COMM_WORLD, fRqLst(i), ierr) 
end do 

do i = 1, 6 
    call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), & 
         lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr) 
end do 
call MPI_WAITALL(6, fRqLst, stLst, ierr) 
call MPI_BARRIER(MPI_COMM_WORLD, ierr) 
end subroutine exchangeData 

someModule是包含所有變量的佔位符模塊。實際上,它們分佈在一系列模塊中,但現在我會詳細介紹。主要想法是使用非阻塞的MPI_IRECV調用來使每個進程接收數據,然後使用一系列阻塞的MPI_SEND調用發送數據。但是,我懷疑如果事情是這麼簡單的話,並行編程本來就是小菜一碟。

該代碼給出了一個SIGABRT並以一個雙免費錯誤退出。此外,它似乎是一隻海森蟲,有時會消失。

錯誤消息:

*** Error in `./a.out': double free or corruption (!prev): 0x00000000010315c0 *** 
*** Error in `./a.out': double free or corruption (!prev): 0x00000000023075c0 *** 
*** Error in `./a.out': double free or corruption (!prev): 0x0000000001d465c0 *** 

Program received signal SIGABRT: Process abort signal. 

Program received signal SIGABRT: Process abort signal. 

Backtrace for this error: 

Program received signal SIGABRT: Process abort signal. 

Backtrace for this error: 

Program received signal SIGABRT: Process abort signal. 

Backtrace for this error: 

Backtrace for this error: 
#0 0x7F5807D3C7D7 
#1 0x7F5807D3CDDE 
#2 0x7F580768ED3F 
#3 0x7F580768ECC9 
#4 0x7F58076920D7 
#5 0x7F58076CB393 
#6 0x7F58076D766D 
#0 0x7F4D387D27D7 
#1 0x7F4D387D2DDE 
#2 0x7F4D38124D3F 
#3 0x7F4D38124CC9 
#4 0x7F4D381280D7 
#5 0x7F4D38161393 
#0 #6 0x7F4D3816D66D 
0x7F265643B7D7 
#1 0x7F265643BDDE 
#2 0x7F2655D8DD3F 
#3 0x7F2655D8DCC9 
#4 0x7F2655D910D7 
#5 0x7F2655DCA393 
#6 0x7F2655DD666D 
#7 0x42F659 in exchangedata_ at solver.f90:1542 (discriminator 1) 
#7 0x42F659 in exchangedata_ at solver.f90:1542 (discriminator 1) 
#8 0x42EFFB in processgrid_ at solver.f90:431 
#9 0x436CF0 in MAIN__ at solver.f90:97 
#8 0x42EFFB in processgrid_ at solver.f90:431 
#9 0x436CF0 in MAIN__ at solver.f90:97 
#0 0x7FC9DA96B7D7 
#1 0x7FC9DA96BDDE 
#2 0x7FC9DA2BDD3F 
#3 0x7FC9DA2BDCC9 
#4 0x7FC9DA2C10D7 
#5 0x7FC9DA2FA393 
#6 0x7FC9DA30666D 
#7 0x42F659 in exchangedata_ at solver.f90:1542 (discriminator 1) 
#8 0x42EFFB in processgrid_ at solver.f90:431 
#9 0x436CF0 in MAIN__ at solver.f90:97 
#7 0x42F659 in exchangedata_ at solver.f90:1542 (discriminator 1) 
#8 0x42EFFB in processgrid_ at solver.f90:431 
#9 0x436CF0 in MAIN__ at solver.f90:97 

我試着在這個網站與「(鑑別1)」部分搜索類似錯誤,但找不到任何。我還搜索了MPI產生雙免費內存損壞錯誤的情況,但又無濟於事。

我還必須指出,錯誤消息中的第1542行對應於我的代碼中的阻塞MPI_SEND調用。

當我在使用gfortran 4.8.2和ompi 1.6.5時出現上述錯誤。不過,我也嘗試運行與英特爾Fortran編譯器上面的代碼並收到一個奇怪的錯誤消息:

[21] trying to free memory block that is currently involved to uncompleted data transfer operation 

我搜索網上的上述錯誤,並得到幾乎沒有。 :(所以這是一個死衚衕,以及完整的錯誤消息是有點長,但下面是它的一部分。

*** glibc detected *** ./a.out: munmap_chunk(): invalid pointer: 0x0000000001c400a0 *** 
*** glibc detected *** ./a.out: malloc(): memory corruption: 0x0000000001c40410 *** 
*** glibc detected *** ./a.out: malloc(): memory corruption: 0x0000000000a67790 *** 
*** glibc detected *** ./a.out: malloc(): memory corruption: 0x0000000000a67790 *** 
*** glibc detected *** ./a.out: free(): invalid next size (normal): 0x0000000000d28c80 *** 
*** glibc detected *** ./a.out: malloc(): memory corruption: 0x00000000015354b0 *** 
*** glibc detected *** ./a.out: malloc(): memory corruption: 0x00000000015354b0 *** 
*** glibc detected *** ./a.out: free(): invalid next size (normal): 0x0000000000f51520 *** 
[20] trying to free memory block that is currently involved to uncompleted data transfer operation 
free mem - addr=0x26bd800 len=3966637480 
RTC entry - addr=0x26a9e70 len=148800 cnt=1 
Assertion failed in file ../../i_rtc_cache.c at line 1397: 0 
internal ABORT - process 20 
[21] trying to free memory block that is currently involved to uncompleted data transfer operation 
free mem - addr=0x951e90 len=2282431520 
RTC entry - addr=0x93e160 len=148752 cnt=1 
Assertion failed in file ../../i_rtc_cache.c at line 1397: 0 
internal ABORT - process 21 

如果我的錯誤是有些粗心的一個或知識不足承擔,上述資料可能就足夠了。如果這是一個更深層次的問題,那麼我會很樂意進一步闡述。

提前感謝!

+1

您必須假設發送/接收將被阻止。他們不需要。使用Ssend可以幫助您調試問題,因爲它始終會阻塞,直到發生匹配。非阻塞幾乎總是做邊界交換的正確方法。 – Jeff

+0

您可以先嚐試使用連續緩衝區。我不太確定Fortran陣列切片是如何工作的。他們應該得到支持,至少在Fortran 2008綁定中,但他們可能是越野車。 – Jeff

+1

非阻塞MPI和非連續數組在Fortran中非常危險。 MPI3仍然得不到很好的支持,特別是對於gfortran。我推薦MPI派生類型並傳遞緩衝區的第一個元素。 –

回答

0

雖然這個問題吸引了有用的意見,我相信張貼在建​​議如何幫助一個答案我解決這個問題對於那些可能在未來遇到同樣問題的人來說很有用。

正如指出的那樣,非阻塞MPI與Fortran的非連續陣列調用 - 壞主意

我使用的非連續的數組複製到一個連續的一個,並使用該代替的想法。但是,對於阻塞調用,非連續數組似乎表現良好。由於我使用的是阻塞MPI_SEND和非阻塞MPI_IRECV,因此代碼只會生成一個副本 - 僅用於接收,並繼續像以前一樣不連續地發送數據。這似乎現在正在工作,但如果稍後可能導致任何打嗝,請在評論中提醒我。

它增加了很多重複的代碼行(破壞了美學:P)。這主要是因爲發送/接收的限制對於所有6個面不相同。因此,用於臨時存儲要接收的數據的陣列必須爲六個面中的每一個分別分配(並複製)。

subroutine exchangeData(dCube) 
use someModule 

implicit none 
integer :: i 
integer, dimension(6) :: fRqLst 
integer :: stLst(MPI_STATUS_SIZE, 6) 
double precision, intent(inout), dimension(xS:xE, yS:yE, zS:zE) :: dCube 
double precision, allocatable, dimension(:,:,:) :: fx0, fx1, fy0, fy1, fz0, fz1 

allocate(fx0(lrArr(1, 1):lrArr(2, 1), lrArr(3, 1):lrArr(4, 1), lrArr(5, 1):lrArr(6, 1))) 
allocate(fx1(lrArr(1, 2):lrArr(2, 2), lrArr(3, 2):lrArr(4, 2), lrArr(5, 2):lrArr(6, 2))) 
allocate(fy0(lrArr(1, 3):lrArr(2, 3), lrArr(3, 3):lrArr(4, 3), lrArr(5, 3):lrArr(6, 3))) 
allocate(fy1(lrArr(1, 4):lrArr(2, 4), lrArr(3, 4):lrArr(4, 4), lrArr(5, 4):lrArr(6, 4))) 
allocate(fz0(lrArr(1, 5):lrArr(2, 5), lrArr(3, 5):lrArr(4, 5), lrArr(5, 5):lrArr(6, 5))) 
allocate(fz1(lrArr(1, 6):lrArr(2, 6), lrArr(3, 6):lrArr(4, 6), lrArr(5, 6):lrArr(6, 6))) 

fRqLst = MPI_REQUEST_NULL 
call MPI_IRECV(fx0, lrArr(7, 1), MPI_DOUBLE_PRECISION, rArr(1), trArr(1), MPI_COMM_WORLD, fRqLst(1), ierr) 
call MPI_IRECV(fx1, lrArr(7, 2), MPI_DOUBLE_PRECISION, rArr(2), trArr(2), MPI_COMM_WORLD, fRqLst(2), ierr) 
call MPI_IRECV(fy0, lrArr(7, 3), MPI_DOUBLE_PRECISION, rArr(3), trArr(3), MPI_COMM_WORLD, fRqLst(3), ierr) 
call MPI_IRECV(fy1, lrArr(7, 4), MPI_DOUBLE_PRECISION, rArr(4), trArr(4), MPI_COMM_WORLD, fRqLst(4), ierr) 
call MPI_IRECV(fz0, lrArr(7, 5), MPI_DOUBLE_PRECISION, rArr(5), trArr(5), MPI_COMM_WORLD, fRqLst(5), ierr) 
call MPI_IRECV(fz1, lrArr(7, 6), MPI_DOUBLE_PRECISION, rArr(6), trArr(6), MPI_COMM_WORLD, fRqLst(6), ierr) 

do i = 1, 6 
    call MPI_SEND(dCube(lsArr(1, i):lsArr(2, i), lsArr(3, i):lsArr(4, i), lsArr(5, i):lsArr(6, i)), & 
         lsArr(7, i), MPI_DOUBLE_PRECISION, rArr(i), tsArr(i), MPI_COMM_WORLD, ierr) 
end do 

call MPI_WAITALL(6, fRqLst, stLst, ierr) 
dCube(lrArr(1, 1):lrArr(2, 1), lrArr(3, 1):lrArr(4, 1), lrArr(5, 1):lrArr(6, 1)) = fx0 
dCube(lrArr(1, 2):lrArr(2, 2), lrArr(3, 2):lrArr(4, 2), lrArr(5, 2):lrArr(6, 2)) = fx1 
dCube(lrArr(1, 3):lrArr(2, 3), lrArr(3, 3):lrArr(4, 3), lrArr(5, 3):lrArr(6, 3)) = fy0 
dCube(lrArr(1, 4):lrArr(2, 4), lrArr(3, 4):lrArr(4, 4), lrArr(5, 4):lrArr(6, 4)) = fy1 
dCube(lrArr(1, 5):lrArr(2, 5), lrArr(3, 5):lrArr(4, 5), lrArr(5, 5):lrArr(6, 5)) = fz0 
dCube(lrArr(1, 6):lrArr(2, 6), lrArr(3, 6):lrArr(4, 6), lrArr(5, 6):lrArr(6, 6)) = fz1 
deallocate(fx0, fx1, fy0, fy1, fz0, fz1) 
end subroutine exchangeData 

這部分消除了我通過在數組中存儲秩和標籤而獲得的優勢。我這樣做主要是爲了使發送和接收呼叫循環。有了這個修復,只有發送調用可以放在循環中。

由於在子程序的每次調用中分配和釋放會浪費時間,因此可以將數組放在模塊中並在代碼的開始處分配。這些限制在每次通話中都不會改變。

當同樣的方法應用於角點和邊緣時,它確實會使代碼膨脹一點,但它似乎正在工作。 :)

感謝您的意見。