Это сложный вопрос. Вы на правильном пути, и да, вам понадобятся разные типы для отправки и получения.
Отправка части проста - если вы отправляете весь подмассив array
, то вам даже не нужен векторный тип; Вы можете отправить все (rows_per_core)*(cols_per_core)
смежных чисел, начиная с &(array[0][0])
(или array[0]
, если хотите).
Это сложная часть, как вы и собрали. Давайте начнем с самого простого случая - предположим, что все делится равномерно, поэтому все блоки имеют одинаковый размер. Тогда вы можете использовать самый helfpul MPI_Type_create_subarray (вы всегда можете сделать это вместе с векторными типами, но для массивов более высокой размерности это становится утомительным, так как вам нужно создать 1 промежуточный тип для каждого измерения массива, кроме последний ...
Кроме того, вместо жесткого кодирования декомпозиции, вы можете использовать также полезный MPI_Dims_create
, чтобы создать декомпозицию ваших рангов как можно более квадратной. Заметка
что это не обязательно связано с MPI_Cart_create, хотя вы можете использовать его для запрошенных измерений. Я собираюсь пропустить материал cart_create здесь не потому, что это бесполезно, а потому, что я хочу сосредоточиться на материале сбора.
Так что, если у всех одинаковый размер array
, root получает одинаковый тип данных от всех, и для получения данных можно использовать очень простой тип подмассива:
MPI_Type_create_subarray(2, whole_array_size, sub_array_size, starts,
MPI_ORDER_C, MPI_FLOAT, &block_type);
MPI_Type_commit(&block_type);
где sub_array_size[] = {rows_per_core, cols_per_core}
, whole_array_size[] = {n,n}
, а здесь starts[]={0,0}
- например , мы просто предположим, что все начинается с начала.
Причина этого в том, что мы можем затем использовать Gatherv для явной установки смещений в массив:
for (int i=0; i<size; i++) {
counts[i] = 1; /* one block_type per rank */
int row = (i % A);
int col = (i / A);
/* displacement into the whole_array */
disps[i] = (col*cols_per_core + row*(rows_per_core)*n);
}
MPI_Gatherv(array[0], rows_per_core*cols_per_core, MPI_FLOAT,
recvptr, counts, disps, resized_type, 0, MPI_COMM_WORLD);
Итак, теперь каждый отправляет свои данные в виде одного фрагмента, и они поступают в тип в правую часть массива. Чтобы это работало, я изменил тип, чтобы его экстент был только одним плавающим, поэтому смещения можно рассчитать в этой единице:
MPI_Type_create_resized(block_type, 0, 1*sizeof(float), &resized_type);
MPI_Type_commit(&resized_type);
Весь код ниже:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<mpi.h>
float **alloc_2d_float( int ndim1, int ndim2 ) {
float **array2 = malloc( ndim1 * sizeof( float * ) );
int i;
if( array2 != NULL ){
array2[0] = malloc( ndim1 * ndim2 * sizeof( float ) );
if( array2[ 0 ] != NULL ) {
for( i = 1; i < ndim1; i++ )
array2[i] = array2[0] + i * ndim2;
}
else {
free( array2 );
array2 = NULL;
}
}
return array2;
}
void free_2d_float( float **array ) {
if (array != NULL) {
free(array[0]);
free(array);
}
return;
}
void init_array2d(float **array, int ndim1, int ndim2, float data) {
for (int i=0; i<ndim1; i++)
for (int j=0; j<ndim2; j++)
array[i][j] = data;
return;
}
void print_array2d(float **array, int ndim1, int ndim2) {
for (int i=0; i<ndim1; i++) {
for (int j=0; j<ndim2; j++) {
printf("%6.2f ", array[i][j]);
}
printf("\n");
}
return;
}
int main(int argc, char ** argv)
{
int size, rank;
int dim_size[2];
int periods[2];
MPI_Datatype block_type, resized_type;
float **array;
float **whole_array;
float *recvptr;
int *counts, *disps;
int n = 10;
int rows_per_core;
int cols_per_core;
int i, j;
int whole_array_size[2];
int sub_array_size[2];
int starts[2];
int A, B;
/* Initialise MPI */
MPI_Init(&argc, &argv);
/* Get the rank for this process, and the number of processes */
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (rank == 0)
{
/* If we're the master process */
whole_array = alloc_2d_float(n, n);
recvptr = &(whole_array[0][0]);
/* Initialise whole array to silly values */
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
whole_array[i][j] = 9999.99;
}
}
print_array2d(whole_array, n, n);
puts("\n\n");
}
/* Create the cartesian communicator */
MPI_Dims_create(size, 2, dim_size);
A = dim_size[1];
B = dim_size[0];
periods[0] = 1;
periods[1] = 1;
rows_per_core = ceil(n / (float) A);
cols_per_core = ceil(n / (float) B);
if (rows_per_core*A != n) {
if (rank == 0) fprintf(stderr,"Aborting: rows %d don't divide by %d evenly\n", n, A);
MPI_Abort(MPI_COMM_WORLD,1);
}
if (cols_per_core*B != n) {
if (rank == 0) fprintf(stderr,"Aborting: cols %d don't divide by %d evenly\n", n, B);
MPI_Abort(MPI_COMM_WORLD,2);
}
array = alloc_2d_float(rows_per_core, cols_per_core);
printf("%d, RpC: %d, CpC: %d\n", rank, rows_per_core, cols_per_core);
whole_array_size[0] = n;
sub_array_size [0] = rows_per_core;
whole_array_size[1] = n;
sub_array_size [1] = cols_per_core;
starts[0] = 0; starts[1] = 0;
MPI_Type_create_subarray(2, whole_array_size, sub_array_size, starts,
MPI_ORDER_C, MPI_FLOAT, &block_type);
MPI_Type_commit(&block_type);
MPI_Type_create_resized(block_type, 0, 1*sizeof(float), &resized_type);
MPI_Type_commit(&resized_type);
if (array == NULL)
{
printf("Problem with array allocation.\nExiting\n");
MPI_Abort(MPI_COMM_WORLD,3);
}
init_array2d(array,rows_per_core,cols_per_core,(float)rank);
counts = (int *)malloc(size * sizeof(int));
disps = (int *)malloc(size * sizeof(int));
/* note -- we're just using MPI_COMM_WORLD rank here to
* determine location, not the cart_comm for now... */
for (int i=0; i<size; i++) {
counts[i] = 1; /* one block_type per rank */
int row = (i % A);
int col = (i / A);
/* displacement into the whole_array */
disps[i] = (col*cols_per_core + row*(rows_per_core)*n);
}
MPI_Gatherv(array[0], rows_per_core*cols_per_core, MPI_FLOAT,
recvptr, counts, disps, resized_type, 0, MPI_COMM_WORLD);
free_2d_float(array);
if (rank == 0) print_array2d(whole_array, n, n);
if (rank == 0) free_2d_float(whole_array);
MPI_Finalize();
}
Незначительная вещь - вам не нужен барьер перед собранием. На самом деле, вам вряд ли когда-либо действительно нужен барьер, и это дорогостоящие операции по нескольким причинам, и они могут скрыть проблемы - мое эмпирическое правило заключается в том, чтобы никогда, никогда не использовать барьеры, если вы точно не знаете, почему это правило необходимо сломан в этом случае. В этом случае, в частности, коллективная подпрограмма gather
выполняет точно такую же синхронизацию, что и барьер, поэтому просто используйте ее.
Теперь перейдем к более сложным вещам. Если вещи не делятся равномерно, у вас есть несколько вариантов. Самый простой, хотя и не обязательно лучший, это просто заполнить массив так, чтобы он делил равномерно, даже если только для этой операции.
Если вы можете сделать так, чтобы количество столбцов делилось равномерно, даже если количество строк не делалось, тогда вы все равно можете использовать collectv и создать векторный тип для каждой части строки, и соответствующее количество строк от каждого процессора. Это будет работать нормально.
Если у вас определенно есть случай, когда ни один из них не может быть рассчитан на деление, и вы не можете заполнить данные для отправки, тогда я вижу три подопции:
- Как предполагает susterpatt, делайте двухточечное. Для небольшого числа задач это хорошо, но по мере увеличения оно будет значительно менее эффективным, чем коллективные операции.
- Создайте коммуникатор, состоящий из всех процессоров, не относящихся к внешним границам, и используйте точно код выше для сбора их кода; и затем данные точка-точка краевых задач.
- Не собирайтесь вообще обрабатывать 0; используйте тип распределенного массива для описания макета массива и используйте MPI-IO для записи всех данных в файл; как только это будет сделано, вы можете иметь нулевой процесс, отображающий данные, если хотите.