Использование MPI_Irecv и MPI_Isend в цикле for

У меня проблема с MPI_Isend и MPI_Irecv. Я работаю над матрицей смежности графа, которая распределяется по строкам. Мы можем предположить, что каждый процессор содержит одну строку. Для каждой пары индексов (i,j) мне нужно отправить и получить 2 целых числа. По сути, мне нужно получить некоторую другую информацию из других строк, чтобы выполнять вычисления. Я новичок в MPI, и здесь он переходит в бесконечный цикл, я даже не уверен, что это правильный способ использования MPI_Isend или MPI_Irecv в цикле for, а также место ожидания ожидания.

В качестве примера предположим, что у нас есть граф с 6 вершинами, поэтому матрица смежности (adjMatrix) будет матрицей 6*6, у нас также есть матрица 6*2 для некоторой другой информации, и, наконец, мы распределяем данных между 6 процессорами. Следовательно:

          |0  20 16 0  6  0 |      |0  1|
          |20 0  0  19 0  6 |      |1  1|
addMatrix=|16 0  0  0  12 0 |    M=|2  1|
          |0  19 0  0  0  12|      |3  1|
          |6  0  12 0  0  9 |      |0  0|
          |0  6  0  12 9  0 |      |1  0|

Распределяем матрицы следующим образом:

P0:       |0  20 16 0  6  0 |      |0  1|

P1:       |20 0  0  19 0  6 |      |1  1|

P2:       |16 0  0  0  12 0 |      |2  1|

P3:       |0  19 0  0  0  12|      |3  1|

P4:       |6  0  12 0  0  9 |      |0  0|

P5:       |0  6  0  12 9  0 |      |1  0|

Теперь каждому процессору необходимо обновить свою часть adjMatrix. Для этого им нужна информация из некоторой части матрицы M, которая находится в других процессорах. Например, для того, чтобы P0 обновил индекс (0,1), равный 20, необходимо иметь доступ к строке 1 матрицы M, равной {1,1}. Следовательно:

P1 должен отправить MLocal[0][0]=1 и MLocal[0][1]=1 на P0, в котором P0 получит их как M_j0 и M_j1 соответственно.

А также

P0 должен отправить MLocal[0][0]=0 и MLocal[0][1]=1 на P1, в котором P1 получит их как M_j0 и M_j1 соответственно.

    for(int i=0;i<rows;i++){
            for (int j=0; j<n; j++)
            {
                int M_j0,M_j1;
                MPI_Isend(&MLocal[i][0], 1, MPI_INT, j, my_rank+i*n+j+0, MPI_COMM_WORLD, &send_request0);
                MPI_Isend(&MLocal[i][1], 1, MPI_INT, j, my_rank+i*n+j+1, MPI_COMM_WORLD, &send_request1);
                MPI_Irecv(&M_j0, 1, MPI_INT, j, my_rank+i*n+j+0, MPI_COMM_WORLD, &recv_request0);
                MPI_Irecv(&M_j1, 1, MPI_INT, j, my_rank+i*n+j+1, MPI_COMM_WORLD, &recv_request1);
                //MPI_Wait(&send_request0, &status);
                //MPI_Wait(&send_request1, &status);
                MPI_Wait(&recv_request0, &status);
                MPI_Wait(&recv_request1, &status);

                 // Do something ...
            }
        }

Затем по предложению Gilles Gouaillardet я изменил эти 4 MPI_Isend и MPI_Irecv на:

    MPI_Sendrecv(&MoatsLocal[i][0], 1, MPI_INT, j, my_rank+i*n+j+0, &M_j0,1, MPI_INT, my_rank, my_rank+i*n+j+0, MPI_COMM_WORLD, &status);
    MPI_Sendrecv(&MoatsLocal[i][1], 1, MPI_INT, j, my_rank+i*n+j+1, &M_j1,1, MPI_INT, my_rank, my_rank+i*n+j+1, MPI_COMM_WORLD, &status);

Но тем не менее, он входит в бесконечный цикл.

ОБНОВИТЬ:

Я обновил код, отчасти проблема была из-за ранжирования процессоров и соответствия тегов. Я исправил эту часть, но, тем не менее, она была склонна к взаимоблокировке, и я думаю, что знаю, в чем проблема. А может и не решить. Если бы у меня было достаточное количество процессоров, чтобы распределить каждую строку по процессору, т.е. n=p, то не было бы никаких проблем. Но проблема в том, что если количество процессоров меньше n, то поток по главной диагонали идет не красиво. Поясняю на примере, допустим у нас 4 процессора и n=6. Предположим, что это распределение:

P0:       |0  20 16 0  6  0 |      |0  1|

P1:       |20 0  0  19 0  6 |      |1  1|
          |16 0  0  0  12 0 |      |2  1|

P2:       |0  19 0  0  0  12|      |3  1|

P3:       |6  0  12 0  0  9 |      |0  0|
          |0  6  0  12 9  0 |      |1  0|

Это то, что происходит через петлю.

Первая итерация:

P0 отправляет и получает в/из P1 информацию для (0,1):"20" и ждет (готово)

P1 отправляет и получает в/из P0 информацию для (1,0):"20" и ждет (готово)

P2 отправляет и получает в/из P1 информацию для (3,1):"19" и ждет

P3 отправляет и получает в/из P0 информацию для (4,1):"6" и ждет

Вторая итерация:

P0 отправляет и получает в/из P1 информацию для (0,2):"16" и ждет

P1 отправляет и получает на/от P2 информацию для (1,3):"19" и ждет (готово)

P2 ждал P1 (3,1): "19", затем просто получил его и готово!

P3 ожидает P0 для (4,1):"6" и ждет

Третья итерация:

P0 ожидает P1 для (0,2):"16"

P1 отправляет и получает в/из P3 информацию для (1,5):"19" и ждет

P2 отправляет и получает на/от P3 информацию для (3,5):"12" и ждет

P3 ожидает P0 для (4,1):"6"

Четвертая итерация:

P0 ожидает P1 для (0,2):"16"

P1 ожидает P3 для (1,5):"19"

P2 ожидает P3 для (3,5):"12"

P3 ожидает P0 для (4,1):"6"

Сейчас все ждут друг друга, не думаю, что это как-то решить. Решение, предложенное ptb, может сработать, я попробую его.

Тем не менее, любая другая идея приветствуется!


person Sarah    schedule 04.04.2018    source источник
comment
вы можете использовать массив из 4 запросов, а затем MPI_Waitall(4, ...);   -  person Gilles Gouaillardet    schedule 04.04.2018
comment
вы также можете использовать два MPI_Sendrecv()   -  person Gilles Gouaillardet    schedule 04.04.2018
comment
@GillesGouaillardet Я сделал, как вы упомянули. Все еще имеет ту же проблему.   -  person Sarah    schedule 04.04.2018
comment
затем загрузите минимально воспроизводимый пример.   -  person Gilles Gouaillardet    schedule 04.04.2018
comment
@GillesGouaillardet Я обновил его. Любая помощь приветствуется.   -  person Sarah    schedule 04.04.2018
comment
Ну, это не минимально воспроизводимый пример   -  person Gilles Gouaillardet    schedule 05.04.2018
comment
также, вы пробовали с MPI_Waitall(4, ...) ? это должно быть менее подвержено взаимоблокировкам.   -  person Gilles Gouaillardet    schedule 05.04.2018
comment
@GillesGouaillardet да, я делаю это прямо сейчас, я обнаружил, где проблема, которая вызывает взаимоблокировку, и я думаю, что нет способа ее решить. Я обновил свои вопросы.   -  person Sarah    schedule 05.04.2018


Ответы (2)


Есть несколько проблем с кодом, который вы разместили

  1. Каждый процессор будет проходить через rows. Однако в вашем описании строки распределены между процессорами, поэтому, вероятно, это ошибка.
  2. Пункт назначения и источники отправки и получения идентичны. Итак, если вы рассматриваете случай, когда j=0, MPI_Isend(...,j,...) означает, что каждый ранг будет отправлять что-то корневому процессу. За этим следует вызов MPI_IRecv(...,j,...), MPI_Wait, что означает, что каждый процесс будет ждать отправки от корневого процесса, которая никогда не приходит.
  3. Вызов MPI_SendRecv имеет ту же фундаментальную проблему.

Проблема в том, что вам нужно, чтобы ваши вызовы send и recv совпадали. Один из способов сделать это (не обязательно самый производительный) состоит в том, чтобы опубликовать все ваши отправки через MPI_Isend в цикле, а затем использовать MPI_Probe, MPI_Recv для обработки каждого ранга recvs (поскольку количество recvs — это количество отправок, вы точно знаете, как это сделать). много). Пример псевдокода:

int send_count = 0;
for (int j=0; j<n; j++) {
  if (matrix_entry[j] != 0) {
    call MPI_Isend(M_local, 2, MPI_INT, j, 0, ...)
    send_count++;
  }
}
while (send_count) {
  MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, comm, status)
  /* get source from status and then call recv */
  MPI_Recv(M_j01, 2, MPI_INTEGER, status(MPI_SOURCE), ...)
 /* Do something with M_j01 */
 send_count--;
} 
person ptb    schedule 05.04.2018
comment
Спасибо, вы правы, ранги и теги пошли не так, я просто исправляю это. Однако у меня была другая проблема, которая вызывает взаимоблокировку. Я обновил свой вопрос и предоставил более подробную информацию. Есть идеи? - person Sarah; 05.04.2018
comment
Вы по-прежнему комбинируете свои операции отправки/получения, а не публикуете все свои отправки, а затем переходите к обработке полученных. То, как вы это написали, по существу является синхронным/блокирующим, поскольку вы вызываете ожидание сразу после отправки. Вы можете сделать это таким образом, но тогда вам нужно будет приложить больше усилий для организации отправки/получения, чтобы они были совместимы. - person ptb; 06.04.2018

Несколько небольших советов:

Вы всегда должны помнить, что каждый процесс является независимым. Между процессами нет синхронизации (ожидайте, если вы поставите MPI_Barrier).

Я действительно не понимаю ваш цикл по строкам (строки = 6?)

Затем все процессы выполняют код.... Это означает: P0,1,2,3,4,5,6 Вызывает ваш sendrecv, все они делают это 6 раз, так как эти вызовы находятся в цикле...

Наконец: каков был бы обычный размер матрицы? Очень плохая идея отправлять много очень маленьких сообщений.

Вы должны разработать свой алгоритм следующим образом: 1) Выясните, какие данные нужны процессу PX для обновления всех его столбцов. 2) выполнить связь, которая собирает эти данные для всех процессов 3) выполнить обновление.

person David Daverio    schedule 09.04.2018