Как использовать MPI для отправки правильного количества объектов производного типа?

Имейте некоторый опыт работы с MPI, но не с некоторыми более сложными аспектами, такими как производные типы, с чем связан мой вопрос.

Код, над которым я работаю, имеет несколько массивов с размерами (-1:nx+2,-1:ny+2,-1:nz+2). Чтобы было понятно, каждый процесс имеет свои значения nx, ny и nz. Существует перекрытие между массивами. Например, x(:,:,-1:2) для одного процесса будет представлять ту же информацию, что и x(:,:,nz-1:nz+2) для процесса, находящегося "под" ним.

Был определен производный тип cell_zface:

idir = 3
sizes = (/nx_glb, ny_glb, nz_glb/)   !These nums are the same for all procs.
subsizes = (/nx, ny, 2/)
mpitype = MPI_DATATYPE_NULL
CALL MPI_TYPE_CREATE_SUBARRAY(3, sizes, subsizes, starts, &
    MPI_ORDER_FORTRAN, mpireal, mpitype, errcode)
CALL MPI_TYPE_COMMIT(mpitype, errcode)
cell_zface = mpitype

Теперь этот производный тип успешно используется в нескольких вызовах MPI_SENDRECV. Например

CALL MPI_SENDRECV( &
        x(-1,-1,   1), 1, cell_zface, proc_z_min, tag, &
        x(-1,-1,nz+1), 1, cell_zface, proc_z_max, tag, &
        comm, status, errcode)

Насколько я понимаю, этот вызов отправляет и получает два «горизонтальных» среза (т.е. срезы x-y) массива между процессами.

Я хочу сделать что-то немного другое, а именно отправить четыре «горизонтальных» среза. Поэтому я пытаюсь

call mpi_send(x(-1,-1,nz-1), 2, cell_zface,    &
                  proc_z_max, rank, comm, mpierr)

с сопроводительным получением.

И, наконец, моя проблема: код запускается, но ошибочно. AFAICT, это отправляет только два горизонтальных среза, хотя я использую «2» вместо «1» в качестве аргумента счетчика. Я могу исправить это, выполнив два вызова mpi_send:

call mpi_send(x(-1,-1,nz-1), 1, cell_zface,    &
                  proc_z_max, rank, comm, mpierr)
call mpi_send(x(-1,-1,nz+1), 1, cell_zface,    &
                  proc_z_max, rank, comm, mpierr)

с сопровождающими получает, но это, конечно, некрасиво.

Итак, почему mpi_send отправляет только два горизонтальных среза, хотя я установил аргумент count равным «2»? И есть ли чистый способ сделать то, что я хочу сделать здесь?


person bob.sacamento    schedule 04.11.2015    source источник
comment
Вы должны понимать, что даже если у вас есть свой тип, под всем этим у вас действительно есть один огромный непрерывный блок памяти. Таким образом, хотя вы можете отправить два типа cell_zface, размер самого типа в памяти равен расстоянию между первой и последней (в 1D) ячейками памяти, используемыми вашим типом. Ака, размер вашего типа на самом деле не nx*ny*nz.   -  person NoseKnowsAll    schedule 05.11.2015
comment
Чтобы выполнить то, что вы хотите сделать, вам нужно указать производному типу данных другую степень. Обратите внимание, что размер типа данных будет таким же, но экстент (или объем памяти, который будут охватывать последовательные версии этого типа данных) не будет. Я считаю, что вы можете сделать это с помощью MPI_Type_create_resized.   -  person NoseKnowsAll    schedule 05.11.2015


Ответы (1)


Каждый тип данных MPI имеет, так сказать, два размера. Одним из них является истинный размер, то есть объем памяти, необходимый для хранения всех значимых данных, на которые ссылается тип данных. Можно думать об этом как о количестве места в фактическом сообщении, которое занимает элемент этого типа данных.

Другой размер - это так называемый экстент. Каждый тип данных в MPI представляет собой набор инструкций типа: «перейти к смещению dispi от предоставленного места в буфере и прочитать/записать элемент базового типа введитеi". Набор всех пар (typei, dispi) называется картой типов типа данных. Минимальное смещение называется нижней границей, а максимальное смещение + размер базового типа при этом смещении + любое необходимое заполнение называется верхней границей. Экстент типа данных представляет собой разницу между верхней и нижней границами и дает размер самой короткой непрерывной области памяти, которая включает в себя все ячейки, к которым обращается тип данных.

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

MPI использует экстент типа данных при доступе к последовательным элементам этого типа данных. Следующее утверждение:

MPI_SEND(buf, n, dtype, ...)

приводит к:

  • MPI берет один элемент типа dtype из местоположения buf в соответствии с правилами, закодированными как карта типов dtype;
  • MPI берет следующий элемент, начиная с позиции buf + extent(dtype);
  • ...
  • MPI берет n-й элемент, начиная с позиции buf + (n-1)*extent(dtype).

Примитивные типы данных, такие как MPI_INTEGER, MPI_REAL и т. д., имеют размер, соответствующий размеру базового типа (INTEGER, REAL и т. д.) + любое заполнение, предусмотренное архитектурой, что позволяет отправлять массивы базового типа, просто указав количество элементов.

Теперь вернемся к вашему делу. Вы создаете тип данных, который охватывает подмассив nx x ny x 2 из массива nx_glb x ny_glb x nz_glb. Размер этого типа данных действительно в nx * ny * 2 раз больше размера mpireal, но экстент на самом деле в nx_glb * ny_glb * nz_glb раз больше размера mpireal. Другими словами:

MPI_SEND(buf, 2, cell_zface, ...)

не будет извлекать два последовательных фрагмента nx x ny x 2 из большого массива по адресу buf. Вместо этого он будет извлекать по одной плите из каждого из двух последовательных массивов размером nx_glb x ny_glb x nz_glb, начиная с позиции (startx, starty, startz< /sub>) в каждом массиве. Если ваша программа не выдает ошибки при запуске, считайте себя счастливчиком.

Теперь самое сложное. MPI позволяет дать каждому типу данных поддельный экстент (поэтому я назвал экстент, определенный ранее, «истинным»), искусственно устанавливая значения нижней и верхней границ. Это не влияет на размер типа данных или его карты типов (т. е. MPI по-прежнему использует те же смещения и манипулирует элементами тех же базовых типов), но влияет на шаги в памяти, которые MPI делает при доступе к последовательным элементам данного типа данных. Раньше установка экстента выполнялась путем «зажатия» типа данных в структуре между элементами специальных псевдотипов MPI_LB и MPI_UB. Начиная с MPI-2, MPI_TYPE_CREATE_RESIZED используется для достижения того же.

integer(kind=MPI_ADDRESS_KIND) :: lb, extent
integer :: newtype

! First obtain the extent of the old type used to construct cell_zface
call MPI_TYPE_GET_EXTENT(mpireal, lb, extent, errcode)
! Adjust the extent of cell_zface
extent = (nx_glb * ny_glb * subsizes(3)) * extent
call MPI_TYPE_CREATE_RESIZED(cell_zface, lb, extent, newtype, errcode)
call MPI_TYPE_COMMIT(newtype, errcode)
! Get rid of the previous type
call MPI_TYPE_FREE(cell_zface, errcode)
cell_zface = newtype

Теперь вы можете использовать cell_zface для отправки нескольких последовательных блоков.

Альтернативный и предположительно более простой подход — установить размер 3-го измерения массива равным размеру 3-го измерения подмассива при вызове MPI_TYPE_CREATE_SUBARRAY:

idir = 3
subsizes = (/nx, ny, 2/)
sizes = (/nx_glb, ny_glb, subsizes(3)/)   !These nums are the same for all procs.
mpitype = MPI_DATATYPE_NULL
CALL MPI_TYPE_CREATE_SUBARRAY(3, sizes, subsizes, starts, &
    MPI_ORDER_FORTRAN, mpireal, mpitype, errcode)
CALL MPI_TYPE_COMMIT(mpitype, errcode)
cell_zface = mpitype

В обоих случаях я предполагаю, что starts(3) равно 0.

person Hristo Iliev    schedule 05.11.2015
comment
Не ожидал, что столько работы уйдет на ответ! Большое спасибо за полный и очень полезный ответ. Ты мужчина! - person bob.sacamento; 05.11.2015
comment
Итак, если я правильно понимаю, шаг этого типа данных в значительной степени равен размеру всего трехмерного массива. Вот вопрос, на который, как я знаю, вы не можете ответить окончательно, но я был бы признателен за любую вашу проницательность: вы, вероятно, поняли, что я не писал этот код. Итак, имеет ли смысл иметь шаг, равный размеру всего массива? Я не могу придумать ни одного приложения, где это было бы полезно. Конечно, иметь шаг, равный одному измерению или произведению двух измерений. Но весь массив? Спасибо. - person bob.sacamento; 05.11.2015
comment
Основное предназначение конструктора типа данных подмассива — использование в MPI-IO. Разные ранги MPI обеспечивают одни и те же размеры массива и задают в нем разные разделы, что дает им (параллельный) доступ к данным массива, хранящимся в виде одного двоичного файла. Экстент такого типа данных намеренно равен размеру всего массива - это позволяет легко читать из набора таких массивов, хранящихся последовательно. Конструктор подмассива также можно использовать для типов данных, представляющих гало-области в декомпозиции предметной области, но нужно быть осторожным и помнить о его происхождении. - person Hristo Iliev; 05.11.2015
comment
Имеет ли смысл этот шаг для вашего кода, это зависит от того, как используется тип данных. Для примера, приведенного в вопросе, ответ определенно нет; имеет больше смысла иметь тип данных, размер которого равен объему памяти, занимаемому двумя плоскостями XY большого массива. - person Hristo Iliev; 05.11.2015
comment
См. этот вопрос для хорошего примера предполагаемого использование MPI_TYPE_CREATE_SUBARRAY (хотя и в C). - person Hristo Iliev; 05.11.2015