Недавно мне задали вопрос, связанный с MPI. Я давно ничего не писал с MPI, так что с удовольствием взялся за решение. И вот что получилось:
Есть массив объектов класса
Надо переслать этот массив с одного процесса на другой, используя MPI. Казалось бы, тривиальная задача. Есть набор бит(байт), и эти биты(байты) надо передать с одного процесса на другой. Но указатель
Почему я решил об этом рассказать? Потому что нашел интересное решение.
Я, как и многие, привык передавать и принимать сообщения (используя MPI) известной длины. Т.е. процесс-отправитель и процесс-получатель, знают сколько элементов и какого типа передаются:
В данном случае нулевой процесс передает 10 элементов типа MPI_INT первому процессу. В свою очередь, первый процесс получает 10 элементов типа MPI_INT от нулевого процесса.
В этой задаче можно поступить таким же образом. Для простоты, пусть будет только 2 процесса:
1)Нулевой процесс, который передает;
2)Первый процесс, который принимает.
Для каждого элемента массива объектов
В последнем MPI_Send-е я передаю элементы типа
Теперь первому процессу нужно получить сначала
Потом… А потом надо узнать количество элементов
(I): перед каждой передачей
(II): получатель может запустить зонд (англ. probe) и определить длину получаемого сообщения:
В этом случае, используя
Чем же отличается вариант (II) от (I)?
Ответ прост: Если сообщение "относительно короткое", то оно сразу же пересылается получателю, а не дожидается, когда “MPI_Recv” придет за этим ним (прошу прощения за мой французский). В этом случае зонд (MPI_Probe) не будет ходить к процессу-отправителю, а посмотрит длину принимаемого сообщения на своем процессе.
!NOTE: “Относительность” длины сообщения отличается у каждой реализации MPI, а так же зависит от типа конфигурации, при сборке MPI библиотеки.
Таким образом, для коротких сообщений эффективнее использовать вариант (II), т.к. не тратиться время на передачу длины сообщения. Если передаются длинные сообщения, то варианты (I) и (II) практически равноценны.
Есть массив объектов класса
molecule:
class molecule
{
public:
int x_local, y_local, z_local;
int *neighbour;
…
};
//Для простоты объяснения- я специально вынес
// эти переменные в видимую область.Надо переслать этот массив с одного процесса на другой, используя MPI. Казалось бы, тривиальная задача. Есть набор бит(байт), и эти биты(байты) надо передать с одного процесса на другой. Но указатель
neighbour у каждого экземпляра объекта molecule в массиве может указывать на область памяти различного размера. Другими словами, количество элементов массива neighbour у каждого объекта класса molecule динамически меняется.Почему я решил об этом рассказать? Потому что нашел интересное решение.
Я, как и многие, привык передавать и принимать сообщения (используя MPI) известной длины. Т.е. процесс-отправитель и процесс-получатель, знают сколько элементов и какого типа передаются:
int length = 10;
int SendMessage[10], RecvMessage[10];
...
if(myid == 0)
MPI_Send(&SendMessage, length, MPI_INT, 1, 123, MPI_COMM_WORLD);
if(myid == 1)
MPI_Recv(&RecvMessage, length, MPI_INT, 0, 123, MPI_COMM_WORLD,
&status);
.В данном случае нулевой процесс передает 10 элементов типа MPI_INT первому процессу. В свою очередь, первый процесс получает 10 элементов типа MPI_INT от нулевого процесса.
В этой задаче можно поступить таким же образом. Для простоты, пусть будет только 2 процесса:
1)Нулевой процесс, который передает;
2)Первый процесс, который принимает.
Для каждого элемента массива объектов
molecule сначала послать x_local, y_local, z_local. А потом отдельно передать массив , на который указывает neighbour:
…
molecule* test_molecule;
…
if(myid == 0)
{
for (i = 0; i < M; i++)
{
MPI_Send(&test_molecule[i].x_local, 3, MPI_INT, 1, 123,
MPI_COMM_WORLD);
MPI_Send(test_molecule[i].neighbour, (n[i] * sizeof(int)),
MPI_CHAR, 1, 321, MPI_COMM_WORLD);
}
}
В последнем MPI_Send-е я передаю элементы типа
MPI_CHAR, т.е. размера 1 байт. Но указываю количество (n[i] * sizeof(int)). Т.о. передается n[i] элементов neighbour, каждого i-ого элемента массива test_molecule.Теперь первому процессу нужно получить сначала
x_local, y_local, z_local. Потом для каждого элемента массива test_molecule определить длину объекта neighbour, выделить под него память и получить его. Казалось бы, тоже все тривиально. В цикле сначала принимаем x_local, y_local, z_local:
MPI_Recv(&test_molecule[i].x_local,3, MPI_INT, 0, 123,
MPI_COMM_WORLD, &status);
Потом… А потом надо узнать количество элементов
neighbour каждого i-ого элемента массива test_molecule. Это можно сделать как минимум двумя способами: (I): перед каждой передачей
test_molecule[i].neighbour, сначала передавать и получать n[i]. (II): получатель может запустить зонд (англ. probe) и определить длину получаемого сообщения:
MPI_Probe(0, 321, MPI_COMM_WORLD, &status);
MPI_Get_count(&status, MPI_CHAR, &SizeRecv);
//выделение памяти для test_molecule[i].neighbour
test_molecule[i].NeighboursAllocation(SizeRecv);
MPI_Recv(test_molecule[i].neighbour, SizeRecv, MPI_CHAR, 0, 321,
MPI_COMM_WORLD, &status);
В этом случае, используя
MPI_Probe, будет получен объект status типа MPI_Status. Но само сообщение не будет получено. Известно, что в объекте status храниться закодированная информация. В частности, длина принимаемого сообщения. Чтобы декодировать эту длину используется функция MPI_Get_count. Далее принимается всё сообщение, используя MPI_Recv.Чем же отличается вариант (II) от (I)?
Ответ прост: Если сообщение "относительно короткое", то оно сразу же пересылается получателю, а не дожидается, когда “MPI_Recv” придет за этим ним (прошу прощения за мой французский). В этом случае зонд (MPI_Probe) не будет ходить к процессу-отправителю, а посмотрит длину принимаемого сообщения на своем процессе.
!NOTE: “Относительность” длины сообщения отличается у каждой реализации MPI, а так же зависит от типа конфигурации, при сборке MPI библиотеки.
Таким образом, для коротких сообщений эффективнее использовать вариант (II), т.к. не тратиться время на передачу длины сообщения. Если передаются длинные сообщения, то варианты (I) и (II) практически равноценны.
