Выводы.
В результате лабораторной работы изучены принципы работы коммуникаторов MPI, создание пользовательских операций редукции и влияние способа организации коллективной операции на поведение распределённой программы.
В ходе работы реализована MPI-программа, разделяющая процессы на две группы через MPI_Comm_split, формирующая локальные значения A и выполняющая коллективную редукцию. Это позволяет одновременно получить минимум в одной группе и максимум в другой с последующей доставкой результатов всем процессам через MPI_Allreduce.
Проведены замеры времени и построены два графика: зависимость среднего времени от числа процессов и график замедления. Анализ показал рост среднего времени при увеличении числа процессов и заметное ухудшение параллельной эффективности в больших конфигурациях. Основной причиной этого выступают расходы на создание и управление коммуникаторами, создание пользовательской операции и стоимость глобальной коллективной операции, а также затраты на синхронизацию и возможная конкуренция за CPU.
В качестве формальной модели представлена сеть Петри, фиксирующая стадии подготовки N и A, синхронный вызов MPI_Comm_split, распределение токенов и коллективную фазу редукции MPI_Allreduce.
В заключение, прямая реализация хорошо иллюстрирует работу коммуникаторов и коллективных операций при небольших запусках, при увеличении числа процессов отмечается снижение масштабируемости.
Приложение а Исходный код программы
Main.c
#include "/opt/homebrew/Cellar/open-mpi/5.0.8/include/mpi.h"
#include <stdio.h>
#include <stdlib.h>
#include <float.h>
#include <time.h>
void minmax_op(void *invec, void *inoutvec, int *len, MPI_Datatype *dtype) {
double *in = (double*)invec;
double *inout = (double*)inoutvec;
for (int i = 0; i < *len; ++i) {
if (i == 0) {
if (in[i] < inout[i]) inout[i] = in[i];
} else {
if (in[i] > inout[i]) inout[i] = in[i];
}
}
}
int main(int argc, char *argv[]) {
MPI_Init(&argc, &argv);
int size, rank;
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (size < 2) {
if (rank == 0) fprintf(stderr, "Error: program requires at least 2 processes.\n");
MPI_Finalize();
return 1;
}
int N = (rank % 2) + 1;
unsigned int seed = (unsigned int)time(NULL) + (unsigned int)(rank * 7919);
srand(seed);
double A = ((double)rand() / (double)RAND_MAX) * 100.0;
MPI_Comm group_comm;
MPI_Comm_split(MPI_COMM_WORLD, N, rank, &group_comm);
double local[2];
local[0] = (N == 1) ? A : DBL_MAX;
local[1] = (N == 2) ? A : -DBL_MAX;
double global[2];
MPI_Op op;
MPI_Op_create(&minmax_op, 1, &op);
MPI_Allreduce(local, global, 2, MPI_DOUBLE, op, MPI_COMM_WORLD);
MPI_Op_free(&op);
if (N == 1) {
printf("Process %d (N=1): my value = %.6f, MIN among A in N=1 group = %.6f\n", rank, A, global[0]);
} else {
printf("Process %d (N=2): my value = %.6f, MAX among A in N=2 group = %.6f\n", rank, A, global[1]);
}
MPI_Comm_free(&group_comm);
MPI_Finalize();
return 0;
}
