MPI_Isend не отправляет сообщение немедленно на MPICH v3.3.2

Я пишу программу для параллельного вычисления матричного умножения и использую функцию MPI_Isend для неблокирующей связи. Вот основная логика распараллеливания::

  • Главный узел отправляет большую часть работы разным рабочим узлам.
  • Тогда он выполняет часть работы сам. Он ожидает, что рабочие тем временем начнут получать свою работу и работать над ней.
  • Он ждет, пока рабочие завершат свою работу, вызывая MPI_Wait, а затем MPI_Recv.

Но на самом деле рабочие получают свои сообщения только тогда, когда главный узел вызывает MPI_Wait; поэтому, когда главный узел выполняет свою часть работы, другие узлы сидят на месте. Почему это происходит?

#include "helpers.h"
#include <mpi.h>


int main(int argc, char const *argv[]) {
    int m = atoi(argv[1]);
    int n = atoi(argv[2]);
    int p = atoi(argv[3]);
    double start, elapsed;

    MPI_Init(NULL, NULL);
    int world_size, world_rank, name_len;
    char processor_name[MPI_MAX_PROCESSOR_NAME];
    MPI_Comm_size(MPI_COMM_WORLD, &world_size);
    MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
    MPI_Get_processor_name(processor_name, &name_len);
    printf("Hello world from processor %s, rank %d out of %d processors\n", processor_name, world_rank, world_size);

    float *A = malloc(m * n * sizeof(float));
    float *B = malloc(n * p * sizeof(float));
    float *C = malloc(m * p * sizeof(float));
    float *C_serial = malloc(m * p * sizeof(float));

    if (world_rank == 0) {
        randarr(m*n, A);
    } else if (world_rank == 1) {
        randarr(n*p, B);
    }
    MPI_Barrier(MPI_COMM_WORLD);
    start = MPI_Wtime();

    // Serial multiplication
    if (!world_rank) {
        Multiply_serial(A, B, C_serial, m, n, p);
        elapsed = MPI_Wtime() - start;
        printf("[*] Serial multiplication: %f seconds\n", elapsed);
    }

    // Parallel multiplication
    int mpart = m / world_size;
    MPI_Request request_ids[(world_size-1)*2];
    float *Apart = malloc(mpart * n * sizeof(float));
    float *Cpart = malloc(mpart * p * sizeof(float));

    // Master node
    if (!world_rank) {
        for (int i = 1; i < world_size; ++i) {
            MPI_Isend(&A[i*mpart*n], mpart*n, MPI_FLOAT, i, 1, MPI_COMM_WORLD, &request_ids[(i-1)*2]);
            MPI_Isend(B, n*p, MPI_FLOAT, i, 2, MPI_COMM_WORLD, &request_ids[(i-1)*2 + 1]);
        }
        printf("[*] Started sending: %f seconds\n", MPI_Wtime() - start);
        // Master node's share of multiplication
        for (int i = 0; i < mpart; ++i) {
            for (int j = 0; j < p; ++j) {
                C[i*p + j] = 0.0;
                for (int k = 0; k < n; ++k) {
                    C[i*p + j] += + A[i*n + k] * B[k*p + j];
                }
            }
        }
        printf("[*] Multiplied: %f seconds\n", MPI_Wtime() - start);
        MPI_Waitall((world_size-1)*2, request_ids, MPI_STATUSES_IGNORE);
        printf("[*] Sending completed: %f seconds\n", MPI_Wtime() - start);
        for (int i = 1; i < world_size; ++i) {
            MPI_Recv(&C[i*mpart*p], mpart*p, MPI_FLOAT, i, 3, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        }

        elapsed = MPI_Wtime() - start;
        printf("[*] Parallel multiplication: %f seconds\n", elapsed);

        int correct = IsEqual(C, C_serial, m, p);
        printf("[*] Parallel correctness: %d\n", correct);
    }

    // Worker nodes 
    if (world_rank) {
        MPI_Recv(Apart, mpart*n, MPI_FLOAT, 0, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        MPI_Recv(B, n*p, MPI_FLOAT, 0, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        printf("[*] I'm worker, got work: %f seconds\n", MPI_Wtime() - start);

        for (int i = 0; i < mpart; ++i) {
            for (int j = 0; j < p; ++j) {
                Cpart[i*p + j] = 0.0;
                for (int k = 0; k < n; ++k) {
                    Cpart[i*p + j] += + Apart[i*n + k] * B[k*p + j];
                }
            }
        }
        MPI_Send(Cpart, mpart*p, MPI_FLOAT, 0, 3, MPI_COMM_WORLD);
    }

    MPI_Finalize();
    return 0;
}

А вот пример запуска программы::

sumit@HAL9000:~/Coding/parallel/parallel-distributed-assignments/A2$ time make mpinb
mpicc mpib.c -o mpib
mpicc mpinb.c -o mpinb
mpirun -n 4 ./mpinb 6000 32 6000
Hello world from processor HAL9000, rank 0 out of 4 processors
Hello world from processor HAL9000, rank 1 out of 4 processors
Hello world from processor HAL9000, rank 2 out of 4 processors
Hello world from processor HAL9000, rank 3 out of 4 processors
[*] Serial multiplication: 4.700643 seconds
[*] Started sending: 4.700674 seconds
[*] Multiplied: 5.790390 seconds
[*] I'm worker, got work: 5.790726 seconds
[*] I'm worker, got work: 5.790901 seconds
[*] Sending completed: 5.790974 seconds
[*] I'm worker, got work: 5.791019 seconds
[*] Parallel multiplication: 6.920345 seconds
[*] Parallel correctness: 1

real    0m7.175s
user    0m27.699s
sys 0m0.479s

person Sumit Ghosh    schedule 26.02.2020    source источник
comment
Ваша реализация MPI не имеет потока выполнения, по крайней мере, с используемым вами межсоединением.   -  person Gilles Gouaillardet    schedule 27.02.2020
comment
@GillesGouaillardet, можете ли вы подробнее рассказать об этом или дать ссылку на какой-либо ресурс? Кроме того, я использую MPICH v3 и запускаю все локально, поэтому межсоединение — это межпроцессное взаимодействие. Можете ли вы направить меня к какому-нибудь ресурсу, чтобы я мог включить поток выполнения для MPICH?   -  person Sumit Ghosh    schedule 28.02.2020
comment
Я только что попробовал тот же код с OpenMPI вместо MPICH, и он работал, как и ожидалось. Так что да, MPICH не имеет надлежащей реализации потока выполнения. Странно, учитывая, что это якобы самая популярная и широко используемая реализация MPI.   -  person Sumit Ghosh    schedule 28.02.2020
comment
из любопытства, вы пробовали MPI_Init_thead(..., MPI_THREAD_MULTIPLE, ...) вместо MPI_Init(...) ? Я не знаком с внутренностями MPICH, но это может помочь.   -  person Gilles Gouaillardet    schedule 28.02.2020


Ответы (1)


Вы запускали его несколько раз, и он вел себя так же?

Попробуйте указать процедуре «rank = 0», чтобы сделать значительную паузу (может быть, 10 секунд?) Пауза непосредственно перед вызовом MPI_Waitall. Потому что я не уверен, что это работает не так, как вы хотите. Я думаю, это может быть просто потому, что процесс связи длиннее, чем выполнение 3-х циклов for, так что рабочие заканчивают свою работу после proc "rank = 0".

PS: я тоже думаю, что 2 ваших сообщения printf не совпадают в вашем коде и в exec («Только что отправлено» и «Отправка выполнена» кажутся «Начата отправка» и «Отправка завершена» вашего кода?)

person Kiwi GM    schedule 27.02.2020
comment
Процесс общения не длиннее 3 циклов for, я в этом уверен. Потому что у меня есть другая реализация этой программы, которая использует блокирующий вызов MPI_Send и работает в два раза быстрее, чем эта. И вы правы, я отредактировал сообщения printf в своем коде, но не опубликовал обновленный вывод. Я исправил это сейчас. - person Sumit Ghosh; 28.02.2020
comment
Как я уже сказал в комментарии к вопросу, код работает с OpenMPI, как и ожидалось. Так что это проблема реализации с MPICH, по-видимому, он не реализует поток выполнения. - person Sumit Ghosh; 28.02.2020