2015-06-03 1 views
1

Код, над которым я работаю, использует MPI для разделения большого 3-мерного массива (куба) на поддомены по всем трем осям для формирования меньших кубов. Ранее я работал над более простым двухмерным эквивалентом без каких-либо проблем.MPI Преобразование блокировки в неблокирующие проблемы

Теперь, когда у MPI есть эта досадная привычка (или удовлетворительная привычка, в зависимости от того, как вы ее видите), рассматривая MPI_SEND и MPI_RECV как неблокирующие вызовы для небольших фрагментов данных, эта миграция из 2D в 3D привела к большой борьбе. Все вызовы, которые отлично работали для 2D, зашли в тупик при малейшей провокации в 3D, поскольку данные, которые должны были передаваться между процессами, теперь были трехмерными массивами и поэтому были большими.

После недели борьбы и вытягивания много волос был создан сложный набор вызовов MPI_SEND и MPI_RECV, которым удалось передать данные по граням, краям и углам каждого куба в домене (с периодичностью и непериодическим набором соответственно на разных границах) плавно. Счастье не продлилось. После добавления нового граничного условия, которое потребовало дополнительного пути связи между ячейками на одной стороне домена, код погрузился в другой порочный приступ взаимоблокировки.

Имея достаточно, я решил прибегнуть к неблокирующим вызовам. Теперь с таким большим опытом, я надеюсь, что мои намерения с приведенным ниже кодом будут достаточно простыми. Я не включаю коды, которые я использовал для передачи данных по краям и углам поддоменов. Если я смогу разобраться в связях между гранями кубов, я смогу принести все остальное, чтобы аккуратно упасть на место.

код использует пять массивов для упрощения передачи данных:

  1. rArr = Array рангов соседних ячеек

  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 

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 

Если моя ошибка некоторыми небрежные один или нести из-за недостаток знаний, выше детали могут быть достаточно. Если это более серьезная проблема, то я с удовольствием буду подробно остановиться.

заранее спасибо!

+1

Вы должны предположить, что Send/Recv будет заблокирован. Они не должны. Использование 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 

Это частично исключает преимущество, которое я пытался получить, сохраняя ранги и метки в массивах. Я сделал это, главным образом, для отправки и приема вызовов в цикле. С помощью этого исправления только вызовы отправки могут быть вставлены в цикл.

Поскольку выделение и освобождение от вызова в каждом вызове подпрограммы может тратить время, массивы могут быть помещены в модуль и выделены в начале кода. Пределы не изменяются при каждом вызове.

Когда тот же метод применяется и для углов и краев, он немного раздувает код, но, похоже, он работает. :)

Спасибо за комментарии.