Код, над которым я работаю, использует MPI для разделения большого 3-мерного массива (куба) на поддомены по всем трем осям для формирования меньших кубов. Ранее я работал над более простым двухмерным эквивалентом без каких-либо проблем.MPI Преобразование блокировки в неблокирующие проблемы
Теперь, когда у MPI есть эта досадная привычка (или удовлетворительная привычка, в зависимости от того, как вы ее видите), рассматривая MPI_SEND и MPI_RECV как неблокирующие вызовы для небольших фрагментов данных, эта миграция из 2D в 3D привела к большой борьбе. Все вызовы, которые отлично работали для 2D, зашли в тупик при малейшей провокации в 3D, поскольку данные, которые должны были передаваться между процессами, теперь были трехмерными массивами и поэтому были большими.
После недели борьбы и вытягивания много волос был создан сложный набор вызовов MPI_SEND и MPI_RECV, которым удалось передать данные по граням, краям и углам каждого куба в домене (с периодичностью и непериодическим набором соответственно на разных границах) плавно. Счастье не продлилось. После добавления нового граничного условия, которое потребовало дополнительного пути связи между ячейками на одной стороне домена, код погрузился в другой порочный приступ взаимоблокировки.
Имея достаточно, я решил прибегнуть к неблокирующим вызовам. Теперь с таким большим опытом, я надеюсь, что мои намерения с приведенным ниже кодом будут достаточно простыми. Я не включаю коды, которые я использовал для передачи данных по краям и углам поддоменов. Если я смогу разобраться в связях между гранями кубов, я смогу принести все остальное, чтобы аккуратно упасть на место.
код использует пять массивов для упрощения передачи данных:
rArr = Array рангов соседних ячеек
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
So вызова для отправки значения переменной dCube поперек с лицом ячейки (процесс) произойдет как бел вл:
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
В основном он идет вдоль каждого направления, х, у и г и сначала отправляет данные из нечетных граней, а затем четные лица. Я не знаю, есть ли более простой способ сделать это. Это было достигнуто после бесчисленных кодов взаимоблокировки, которые чуть не сводили меня с ума. Коды для отправки данных по краям и углам еще длиннее.
Теперь возникает актуальная проблема, с которой я столкнулся сейчас. Я заменил код выше со следующими (немного наивно, может быть?)
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. Тем не менее, я также попытался запустить приведенный выше код с Фортраном компилятором Intel и получил любопытное сообщение об ошибке:
[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
Если моя ошибка некоторыми небрежные один или нести из-за недостаток знаний, выше детали могут быть достаточно. Если это более серьезная проблема, то я с удовольствием буду подробно остановиться.
заранее спасибо!
Вы должны предположить, что Send/Recv будет заблокирован. Они не должны. Использование Ssend может помочь вам отладить проблему, поскольку она всегда будет блокироваться до тех пор, пока не произойдет сопоставление. Неблокирование - это почти всегда правильный путь для пограничного обмена. – Jeff
Сначала вы можете попробовать со смежным буфером. Я не совсем уверен, как работают срезы массива Fortran. Предполагается, что они поддерживаются, по крайней мере, с привязками Fortran 2008, но они могут быть ошибочными. – Jeff
Неблокирующий MPI и несмежные массивы очень опасны в Fortran. MPI3 по-прежнему не поддерживается хорошо, особенно для gfortran. Я рекомендую типы, основанные на MPI, и передаю только первый элемент буфера. –