我正在使用的代碼使用MPI將大型3維陣列(立方體)分成沿着所有三個軸的子域以形成更小的立方體。我以前曾經在一個簡單的二維等價物上工作,沒有問題。MPI將阻塞轉換爲非阻塞問題
現在,由於MPI將MPI_SEND和MPI_RECV視爲非阻塞調用小塊數據的煩人習慣(或令人滿意的習慣,取決於您如何看待它),這種從2D到3D的遷移帶來了很多困難。所有對2D都非常好用的調用在3D中最輕微的挑釁時開始出現死鎖,因爲必須在進程之間傳遞的數據現在是3D數組,因此數據量更大。
經過一週的鬥爭並拉出大量頭髮後,構建了一組複雜的MPI_SEND和MPI_RECV調用,它們設法將數據傳遞到域中每個立方體的面,邊和角落(設置了週期性和非週期性設置適當地在不同的邊界)。幸福不會持久。在添加一個新的邊界條件後,該邊界條件需要域之間的單元格之間的額外通信路徑,代碼陷入了另一個死鎖的惡性循環。
已經夠了,我決定訴諸非阻塞電話。現在有了這麼多的背景,我希望我的意圖與下面的代碼將是很簡單的。我不包括我用來在子域的邊緣和角落之間傳輸數據的代碼。如果我能理清立方體表面之間的溝通,我將能夠把其他所有東西都整齊地落入到位。
該代碼使用五個陣列以簡化數據傳輸:
RARR =陣列相鄰小區
tsArr =標籤用於將數據發送到每個相鄰
陣列的行列的trArr =用於接收來自每個鄰居的數據的標籤陣列
lsArr =限制數組(i ndices)來描述的數據塊要發送
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
如果我的錯誤是有些粗心的一個或知識不足承擔,上述資料可能就足夠了。如果這是一個更深層次的問題,那麼我會很樂意進一步闡述。
提前感謝!
您必須假設發送/接收將被阻止。他們不需要。使用Ssend可以幫助您調試問題,因爲它始終會阻塞,直到發生匹配。非阻塞幾乎總是做邊界交換的正確方法。 – Jeff
您可以先嚐試使用連續緩衝區。我不太確定Fortran陣列切片是如何工作的。他們應該得到支持,至少在Fortran 2008綁定中,但他們可能是越野車。 – Jeff
非阻塞MPI和非連續數組在Fortran中非常危險。 MPI3仍然得不到很好的支持,特別是對於gfortran。我推薦MPI派生類型並傳遞緩衝區的第一個元素。 –