Выводы.
В результате выполнения лабораторной работы изучены принципы блочного параллельного умножения матриц, основные идеи алгоритма Кэннона и влияние коммуникационных затрат на эффективность параллельных расчётов.
В ходе работы реализована простая последовательная версия умножения матриц и параллельная реализация блочного алгоритма Кэннона с разбиением на блоки, начальным выравниванием блоков и циклическими сдвигами.
Были проведены измерения времени работы программы и построен график ускорения. Анализ графика показал, что для очень малых размеров матриц (m = 10, 50, 100) накладные расходы на распределение данных и обмены доминируют - ускорение мало или меньше единицы. При увеличении размера задачи выигрыши от распараллеливания становятся заметными: например, при m = 5000 последовательное время ≈237.67 с, время на 16 процессах ≈32.44 с (ускорение ≈7.72), на 64 процессах ≈31.00 с (ускорение ≈7.62), то есть при росте числа процессов ускорение растёт до некоторого предела и затем наступает насыщение. Для m = 1000 при увеличении числа процессов наблюдался явный рост ускорения при переходе от 4 к 16 процессам (примерно с ≈3.9 до ≈6.8). Такая картина согласуется с моделью, в которой вычислительная часть уменьшается при распараллеливании, а коммуникационная часть ограничивает дальнейшую масштабируемость.
В качестве формальной модели процесса обмена была построена сеть Петри, отражающая только основные этапы: распределение данных, начальное выравнивание блоков, последовательные вычислительные шаги и циклические сдвиги - модель используется для формального представления и анализа обменов между процессами.
Приложение а Исходный код программы
Main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <mpi.h>
typedef long long val_t;
void seq_matmul(const val_t *A, const val_t *B, val_t *C, int m) {
for (int i = 0; i < m*m; ++i) C[i] = 0;
for (int i = 0; i < m; ++i) {
for (int k = 0; k < m; ++k) {
val_t a = A[i*m + k];
for (int j = 0; j < m; ++j) {
C[i*m + j] += a * B[k*m + j];
}
}
}
}
void local_block_mul_add(const val_t *A, const val_t *B, val_t *C, int bs) {
for (int i = 0; i < bs; ++i) {
for (int k = 0; k < bs; ++k) {
val_t a = A[i*bs + k];
for (int j = 0; j < bs; ++j) {
C[i*bs + j] += a * B[k*bs + j];
}
}
}
}
void *xmalloc(size_t s) {
void *p = malloc(s);
if (!p) { fprintf(stderr, "malloc failed\n"); MPI_Abort(MPI_COMM_WORLD, 1); }
return p;
}
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
int world_rank, world_size;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
int m = 0;
val_t *A = NULL, *B = NULL, *Cseq = NULL;
if (world_rank == 0) {
if (scanf("%d", &m) != 1) {
fprintf(stderr, "Не удалось прочитать m\n");
MPI_Abort(MPI_COMM_WORLD, 1);
}
if (m <= 0) {
fprintf(stderr, "m должно быть > 0\n");
MPI_Abort(MPI_COMM_WORLD, 1);
}
A = (val_t*)xmalloc(sizeof(val_t) * m * m);
B = (val_t*)xmalloc(sizeof(val_t) * m * m);
for (int i = 0; i < m*m; ++i) {
if (scanf("%lld", &A[i]) != 1) { fprintf(stderr, "Bad input for A\n"); MPI_Abort(MPI_COMM_WORLD,1); }
}
for (int i = 0; i < m*m; ++i) {
if (scanf("%lld", &B[i]) != 1) { fprintf(stderr, "Bad input for B\n"); MPI_Abort(MPI_COMM_WORLD,1); }
}
}
MPI_Bcast(&m, 1, MPI_INT, 0, MPI_COMM_WORLD);
if (world_size == 1) {
if (world_rank == 0) {
Cseq = (val_t*)xmalloc(sizeof(val_t) * m * m);
double t0 = MPI_Wtime();
seq_matmul(A, B, Cseq, m);
double t1 = MPI_Wtime();
double serial_time = t1 - t0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < m; ++j) {
if (j) printf(" ");
printf("%lld", Cseq[i*m + j]);
}
printf("\n");
}
fprintf(stderr, "Serial time: %f s\n", serial_time);
free(A); free(B); free(Cseq);
}
MPI_Finalize();
return 0;
}
int q = (int)(floor(sqrt((double)world_size) + 0.5));
if (q * q != world_size) {
if (world_rank == 0) fprintf(stderr, "Число процессов (%d) не является полным квадратом\n", world_size);
MPI_Abort(MPI_COMM_WORLD, 2);
}
int reorder = 0;
int dims[2] = { q, q };
int periods[2] = { 1, 1 };
MPI_Comm comm2d;
MPI_Cart_create(MPI_COMM_WORLD, 2, dims, periods, reorder, &comm2d);
int cart_rank;
MPI_Comm_rank(comm2d, &cart_rank);
int coords[2];
MPI_Cart_coords(comm2d, cart_rank, 2, coords);
int my_row = coords[0], my_col = coords[1];
int root_cart = -1;
if (world_rank == 0) root_cart = cart_rank;
MPI_Bcast(&root_cart, 1, MPI_INT, 0, MPI_COMM_WORLD);
int bs = (m + q - 1) / q;
int n = bs * q;
val_t *A_padded = NULL, *B_padded = NULL;
if (world_rank == 0) {
A_padded = (val_t*)calloc((size_t)n * n, sizeof(val_t));
B_padded = (val_t*)calloc((size_t)n * n, sizeof(val_t));
if (!A_padded || !B_padded) { fprintf(stderr, "calloc failed\n"); MPI_Abort(MPI_COMM_WORLD, 1); }
for (int i = 0; i < m; ++i)
for (int j = 0; j < m; ++j) {
A_padded[i*n + j] = A[i*m + j];
B_padded[i*n + j] = B[i*m + j];
}
}
val_t *localA = (val_t*)xmalloc(sizeof(val_t) * bs * bs);
val_t *localB = (val_t*)xmalloc(sizeof(val_t) * bs * bs);
val_t *localC = (val_t*)xmalloc(sizeof(val_t) * bs * bs);
for (int i = 0; i < bs*bs; ++i) localC[i] = 0;
if (world_rank == 0) {
for (int brow = 0; brow < q; ++brow) {
for (int bcol = 0; bcol < q; ++bcol) {
val_t *tmpA = (val_t*)xmalloc(sizeof(val_t) * bs * bs);
val_t *tmpB = (val_t*)xmalloc(sizeof(val_t) * bs * bs);
for (int i = 0; i < bs; ++i) {
for (int j = 0; j < bs; ++j) {
int gi = brow * bs + i;
int gj = bcol * bs + j;
if (gi < n && gj < n) {
tmpA[i*bs + j] = A_padded[gi*n + gj];
tmpB[i*bs + j] = B_padded[gi*n + gj];
} else {
tmpA[i*bs + j] = 0;
tmpB[i*bs + j] = 0;
}
}
}
int dest_coords[2] = {brow, bcol};
int dest_cart;
MPI_Cart_rank(comm2d, dest_coords, &dest_cart);
if (dest_cart == cart_rank && world_rank == 0) {
memcpy(localA, tmpA, sizeof(val_t)*bs*bs);
memcpy(localB, tmpB, sizeof(val_t)*bs*bs);
} else {
MPI_Send(tmpA, bs*bs, MPI_LONG_LONG, dest_cart, 100, comm2d);
MPI_Send(tmpB, bs*bs, MPI_LONG_LONG, dest_cart, 101, comm2d);
}
free(tmpA); free(tmpB);
}
}
} else {
MPI_Recv(localA, bs*bs, MPI_LONG_LONG, root_cart, 100, comm2d, MPI_STATUS_IGNORE);
MPI_Recv(localB, bs*bs, MPI_LONG_LONG, root_cart, 101, comm2d, MPI_STATUS_IGNORE);
}
MPI_Barrier(comm2d);
double t0 = MPI_Wtime();
MPI_Status status;
int src, dst;
MPI_Cart_shift(comm2d, 1, -my_row, &src, &dst);
MPI_Sendrecv_replace(localA, bs*bs, MPI_LONG_LONG, dst, 1, src, 1, comm2d, &status);
MPI_Cart_shift(comm2d, 0, -my_col, &src, &dst);
MPI_Sendrecv_replace(localB, bs*bs, MPI_LONG_LONG, dst, 2, src, 2, comm2d, &status);
for (int step = 0; step < q; ++step) {
local_block_mul_add(localA, localB, localC, bs);
MPI_Cart_shift(comm2d, 1, -1, &src, &dst);
MPI_Sendrecv_replace(localA, bs*bs, MPI_LONG_LONG, dst, 11, src, 11, comm2d, &status);
MPI_Cart_shift(comm2d, 0, -1, &src, &dst);
MPI_Sendrecv_replace(localB, bs*bs, MPI_LONG_LONG, dst, 12, src, 12, comm2d, &status);
}
double t1 = MPI_Wtime();
double local_elapsed = t1 - t0;
double max_elapsed;
MPI_Reduce(&local_elapsed, &max_elapsed, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);
if (world_rank == 0) {
val_t *C_padded = (val_t*)calloc((size_t)n * n, sizeof(val_t));
if (!C_padded) { fprintf(stderr, "calloc failed\n"); MPI_Abort(MPI_COMM_WORLD, 1); }
for (int i = 0; i < bs; ++i)
for (int j = 0; j < bs; ++j) {
int gi = my_row * bs + i, gj = my_col * bs + j;
C_padded[gi*n + gj] = localC[i*bs + j];
}
for (int brow = 0; brow < q; ++brow) {
for (int bcol = 0; bcol < q; ++bcol) {
int src_coords[2] = {brow, bcol};
int src_cart; MPI_Cart_rank(comm2d, src_coords, &src_cart);
if (src_cart == cart_rank && world_rank == 0) continue;
val_t *tmp = (val_t*)xmalloc(sizeof(val_t) * bs * bs);
MPI_Recv(tmp, bs*bs, MPI_LONG_LONG, src_cart, 200, comm2d, MPI_STATUS_IGNORE);
for (int i = 0; i < bs; ++i)
for (int j = 0; j < bs; ++j) {
int gi = brow*bs + i, gj = bcol*bs + j;
C_padded[gi*n + gj] = tmp[i*bs + j];
}
free(tmp);
}
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < m; ++j) {
if (j) printf(" ");
printf("%lld", C_padded[i*n + j]);
}
printf("\n");
}
fprintf(stderr, "Parallel time: %f s\n", max_elapsed);
free(C_padded);
} else {
MPI_Send(localC, bs*bs, MPI_LONG_LONG, root_cart, 200, comm2d);
}
free(localA); free(localB); free(localC);
if (world_rank == 0) { free(A_padded); free(B_padded); free(A); free(B); }
MPI_Comm_free(&comm2d);
MPI_Finalize();
return 0;
}
