Эффективная по времени модель проектирования для отправки и получения от всех процессов MPI: MPI все 2 все коммуникации - PullRequest
1 голос
/ 24 мая 2019

Я пытаюсь отправить сообщение всем процессам MPI из процесса, а также получить сообщение от всех этих процессов в процессе. В основном это общение со всеми, где каждый процесс отправляет сообщение каждому другому процессу (кроме себя) и получает сообщение от любого другого процесса.

В следующем примере кода показано, чего я пытаюсь достичь. Теперь проблема с MPI_Send заключается в его поведении, когда для небольшого размера сообщения оно действует как неблокирующее, но для более крупного сообщения (на моей машине BUFFER_SIZE 16400) оно блокируется. Я в курсе, как ведет себя MPI_Send. В качестве обходного пути я заменил приведенный ниже код на блокировку (send + recv), которая называется MPI_Sendrecv. Пример кода такой: MPI_Sendrecv(intSendPack, BUFFER_SIZE, MPI_INT, processId, MPI_TAG, intReceivePack, BUFFER_SIZE, MPI_INT, processId, MPI_TAG, MPI_COMM_WORLD, MPI_STATUSES_IGNORE). Я делаю вышеупомянутый вызов для всех процессов MPI_COMM_WORLD внутри цикла для каждого ранга, и этот подход дает мне то, что я пытаюсь достичь (все для всех коммуникаций). Тем не менее, этот вызов занимает много времени, которое я хочу сократить с некоторым эффективным подходом времени. Я попытался с mpi scatter и collect, чтобы выполнить все для всех коммуникаций, но здесь одна проблема заключается в том, что размер буфера (16400) может отличаться в реальной реализации на разных итерациях для вызова функции MPI_all_to_all. Здесь я использую MPI_TAG для дифференциации вызова в разных итерациях, которые я не могу использовать в функциях разброса и сбора.

#define BUFFER_SIZE 16400

void MPI_all_to_all(int MPI_TAG)
{

    int size;
    int rank;
    MPI_Comm_size(MPI_COMM_WORLD, &size);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    int* intSendPack = new int[BUFFER_SIZE]();
    int* intReceivePack = new int[BUFFER_SIZE]();

    for (int prId = 0; prId < size; prId++) {
        if (prId != rank) {
            MPI_Send(intSendPack, BUFFER_SIZE, MPI_INT, prId, MPI_TAG,
            MPI_COMM_WORLD);
          }
    }

    for (int sId = 0; sId < size; sId++) {
        if (sId != rank) {
            MPI_Recv(intReceivePack, BUFFER_SIZE, MPI_INT, sId, MPI_TAG,
            MPI_COMM_WORLD, MPI_STATUSES_IGNORE);
        }
    }
}

Я хочу знать, есть ли способ, которым я могу выполнить все коммуникации, используя любую эффективную модель коммуникации. Я не придерживаюсь MPI_Send, если есть какой-то другой способ, который дает мне то, чего я пытаюсь достичь, я рад этому. Любая помощь или предложение высоко ценится.

1 Ответ

0 голосов
/ 05 июня 2019

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

#include <iostream>
#include <algorithm>
#include <mpi.h>

#define BUFFER_SIZE 16384

void point2point(int*, int*, int, int);

int main(int argc, char *argv[])
{
    MPI_Init(&argc, &argv);

    int rank_id = 0, com_sz = 0;
    double t0 = 0.0, tf = 0.0;
    MPI_Comm_size(MPI_COMM_WORLD, &com_sz);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank_id);

    int* intSendPack = new int[BUFFER_SIZE]();
    int* result = new int[BUFFER_SIZE*com_sz]();
    std::fill(intSendPack, intSendPack + BUFFER_SIZE, rank_id);
    std::fill(result + BUFFER_SIZE*rank_id, result + BUFFER_SIZE*(rank_id+1), rank_id);

    // Send-Receive
    t0 = MPI_Wtime();
    point2point(intSendPack, result, rank_id, com_sz);
    MPI_Barrier(MPI_COMM_WORLD);
    tf = MPI_Wtime();
    if (!rank_id)
        std::cout << "Send-receive time: " << tf - t0 << std::endl;

    // Collective
    std::fill(result, result + BUFFER_SIZE*com_sz, 0);
    std::fill(result + BUFFER_SIZE*rank_id, result + BUFFER_SIZE*(rank_id+1), rank_id);
    t0 = MPI_Wtime();
    MPI_Allgather(intSendPack, BUFFER_SIZE, MPI_INT, result, BUFFER_SIZE, MPI_INT, MPI_COMM_WORLD);
    MPI_Barrier(MPI_COMM_WORLD);
    tf = MPI_Wtime();
    if (!rank_id)
        std::cout << "Allgather time: " << tf - t0 << std::endl;

    MPI_Finalize();
    delete[] intSendPack;
    delete[] result;
    return 0;
}

// Send/receive communication
void point2point(int* send_buf, int* result, int rank_id, int com_sz)
{
    MPI_Status status;
    // Exchange and store the data
    for (int i=0; i<com_sz; i++){
        if (i != rank_id){
            MPI_Sendrecv(send_buf, BUFFER_SIZE, MPI_INT, i, 0, 
                result + i*BUFFER_SIZE, BUFFER_SIZE, MPI_INT, i, 0, MPI_COMM_WORLD, &status);
        }
    }
}

Здесь каждый ранг добавляет свой собственный массив intSendPack в массив result во всех других рангах, который должен заканчиваться одинаково во всех рангах. result является плоским, каждый ранг занимает BUFFER_SIZE записей, начинающихся с rank_id*BUFFER_SIZE. После связи точка-точка массив возвращается к своей первоначальной форме.

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

Я провел тест на 1 узле Nersc Cori KNL , используя slurm . Я запускал его несколько раз в каждом случае, просто чтобы убедиться, что значения согласованы, и я не смотрю на выбросы, но вы должны запустить его примерно 10 раз или около того, чтобы собрать более правильную статистику.

Вот некоторые мысли:

  • Для небольшого числа процессов (5) и большого размера буфера (16384) коллективное взаимодействие происходит примерно в два раза быстрее, чем точка-точка, но оно становится примерно в 4-5 раз быстрее при переходе на большее количество рангов (64 ).
  • В этом тесте нет большой разницы между производительностью с рекомендованными настройками slurm на этой конкретной машине и настройками по умолчанию, но в реальных, более крупных программах с большим количеством связи есть очень значительная (работа, которая выполняется менее минуты с рекомендуемой будет работать в течение 20-30 минут и более по умолчанию). Смысл в том, чтобы проверить ваши настройки, это может иметь значение.
  • То, что вы видели при отправке / получении больших сообщений, фактически зашло в тупик. Я видел это также для размера сообщения, показанного в этом тесте. В случае, если вы пропустили это, на нем есть два достойных сообщения: объяснение буферизации и слово о блокировке .

Таким образом, настройте этот эталонный тест для более точного представления вашего кода и запуска его в вашей системе, но коллективное взаимодействие в ситуациях «все ко всем» или «один ко всем» должно быть быстрее из-за специальных оптимизаций, таких как превосходные алгоритмы используется для организации связи. Значительное ускорение в 2-5 раз, так как общение часто способствует увеличению общего времени.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...