Как объединить подмассивы различной ширины, используя только один массив для отправки и получения в MPI - PullRequest
2 голосов
/ 01 марта 2012

У меня ужасная проблема с этой проблемой: используя MPI, я хочу объединить несколько смежных непересекающихся столбчатых фрагментов двумерного массива, распределенных в нескольких процессах MPI, в один массив, находящийся в корневом процессе. Основным условием является то, что массив должен быть одинаковым для всех отправляющих и получающих процессов. Второе условие заключается в том, что столбчатые порции, отправляемые каждым процессом, могут иметь разную ширину. Это кажется распространенной проблемой в параллельном программировании, так как я видел по крайней мере 6 вопросов, касающихся этой проблемы, опубликованных в StackOverflow. Ни один из ответов не помог мне, к сожалению. Я могу довольно хорошо решить этот проект, когда делю проблему на куски строки, но не на столбцы. Я понимаю, что это связано с различными шагами в случае столбчатых подмассивов. Я пробовал MPI векторные и подмассивные типы, но безрезультатно.

Используя упрощенную версию моего кода, если я выполню его с COLUMNS, равным 6, я получу:

    0:  1  1  1  2  2  2  
    1:  1  1  1  2  2  2  
    2:  1  1  1  2  2  2  
    3:  1  1  1  2  2  2  
    4:  1  1  1  2  2  2  
    5:  1  1  1  2  2  2  
    6:  1  1  1  2  2  2  

что я и хочу.

С другой стороны, если я выполню его с COLUMNS = 5, я ожидаю получить:

    0:  1  1  1  2  2
    1:  1  1  1  2  2
    2:  1  1  1  2  2
    3:  1  1  1  2  2
    4:  1  1  1  2  2
    5:  1  1  1  2  2
    6:  1  1  1  2  2

Вместо этого я получаю:

    0:  1  1  1  2  2
    1:  2  1  1  2  2
    2:  2  1  1  2  2
    3:  2  1  1  2  2
    4:  2  1  1  2  2
    5:  1  1  1 -0 -0
    6:  1  1  1 -0 -0

Листинг упрощенного кода:

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define ROWS                    7
#define COLUMNS                 6 // 5 or 6 only. I could pass this in the cmd line...
#define NR_OF_PROCESSES         2

void print_matrix (float ** X, int rows, int cols)
{
    for (int i = 0; i < rows; ++i) {
        printf ("%3d: ", i);
        for (int j = 0; j < cols; ++j)
            printf ("%2.0f ", X[i][j]);
        printf ("\n");
    }
}

float **allocate_matrix (int rows, int cols)
{
    float  *data   = (float  *) malloc (rows * cols * sizeof(float));
    float **matrix = (float **) malloc (rows * sizeof(float *));
    for (int i = 0; i < rows; i++)
        matrix[i] = & (data[i * cols]);
    return matrix;
}

int main (int argc, char *argv[])
{
    int   num_procs, my_rank, i, j, root = 0, ncols, ndims = 2, strts;
    float **matrix;
    MPI_Datatype sendsubarray, recvsubarray, resizedrecvsubarray;

    assert (COLUMNS == 5 || COLUMNS == 6);

    MPI_Init (&argc, &argv);
    MPI_Comm_size (MPI_COMM_WORLD, &num_procs);
    if (num_procs != NR_OF_PROCESSES) MPI_Abort (MPI_COMM_WORLD, -1);
    MPI_Comm_rank (MPI_COMM_WORLD, &my_rank);

    ncols = (my_rank == root) ? 3 : COLUMNS - 3;
    strts = (my_rank == root) ? 0 : 3;
    int sizes[2]    = {ROWS, COLUMNS};
    int subsizes[2] = {ROWS, ncols};
    int starts[2]   = {0, strts};

    // Create and populate the matrix at each node (incl. the root):
    matrix = allocate_matrix (ROWS, COLUMNS);
    for (i = 0; i < ROWS; i++)
        for (j = 0; j < COLUMNS; j++)
            matrix[i][j] = my_rank * -1.0;
    for (i = starts[0]; i < starts[0] + subsizes[0]; i++)
        for (j = starts[1]; j < starts[1] + subsizes[1]; j++)
            matrix[i][j] = my_rank + 1.0;

    // Create the subarray type for use by each send node (incl. the root):
    MPI_Type_create_subarray (ndims, sizes, subsizes, starts, MPI_ORDER_C,
                              MPI_FLOAT, &sendsubarray);
    MPI_Type_commit (&sendsubarray);

    // Create the subarray type for use by the receive node (the root):
    if (my_rank == root) {
        MPI_Type_create_subarray (ndims, sizes, subsizes, starts, MPI_ORDER_C,
                                  MPI_FLOAT, &recvsubarray);
        MPI_Type_commit (&recvsubarray);
        MPI_Type_create_resized (recvsubarray, 0, 1 * sizeof(float),
                                 &resizedrecvsubarray);
        MPI_Type_commit (&resizedrecvsubarray);
    }

    // Gather the send matrices into the receive matrix:
    int counts[NR_OF_PROCESSES] = {3, COLUMNS - 3};
    int displs[NR_OF_PROCESSES] = {0, 3};
    MPI_Gatherv (matrix[0], 1, sendsubarray,
                 matrix[0], counts, displs, resizedrecvsubarray,
                 root, MPI_COMM_WORLD);

    // Have the root send the main array to the output:
    if (my_rank == root) print_matrix (matrix, ROWS, COLUMNS);

    // Free out all the allocations we created in this node...
    if (my_rank == 0) {
        MPI_Type_free (&resizedrecvsubarray);
        MPI_Type_free (&recvsubarray);
    }
    MPI_Type_free (&sendsubarray);
    free (matrix);

    MPI_Finalize();
    return 0;
}

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

Любая помощь будет высоко ценится!

1 Ответ

3 голосов
/ 01 марта 2012

Отлично сделано! Там много подтасовок деталей MPI, и там не хватало только одной последней вещи - мне нужно было только добавить две строки и изменить третью, чтобы ваш код заработал.

Тот факт, что у вас в основном все работает, подтверждается даже в искаженном выводе; получено правильное число «2», поэтому вы создаете тип отправки и правильно отправляете свои данные. Единственный трюк в получении.

Из кода Гатерва,

int counts[NR_OF_PROCESSES] = {3, COLUMNS - 3};
int displs[NR_OF_PROCESSES] = {0, 3};

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

Единственное препятствие было в вашей конструкции типа получения подмассива; когда вы делаете этот звонок

    MPI_Type_create_subarray (ndims, sizes, subsizes, starts, MPI_ORDER_C,
                              MPI_FLOAT, &recvsubarray);

вы создаете тип получения, который для процесса получения - размер, размер и смещение данных, которые он отправляет! Вместо этого вы просто хотите создать тип подмассива получения ровно из одного столбца и с началом {0,0} - так что нет (внутреннего) смещения, так что вы можете просто указать его туда, куда нужно идти с вашими смещениями :

    int colsubsizes[]={ROWS, 1};
    int colstarts[]={0,0};
    MPI_Type_create_subarray (ndims, sizes, colsubsizes, colstarts, MPI_ORDER_C,
                              MPI_FLOAT, &recvsubarray);

Когда я запускаю его с этим, он работает.

(Как (намного) более незначительное примечание, вам не нужно фиксировать или, таким образом, освобождать, recvsubarray, так как вы никогда не используете его для реального общения; он используется только при создании типа resizedrecvsubarray, затем передается.)

...