Você está na página 1de 10

ALGORITMOS PARALELOS DE MULTIPLICACIÓN DE

MATRICES

Guido Casco

Electiva V, Diseño de Algoritmos Paralelos, Noveno Semestre,

guiancs82@gmail.com

Claudio Lezcano

Electiva V, Diseño de Algoritmos Paralelos, Noveno Semestre,

claudiogmi@gmail.com

Resumen. El documento presentado trata acerca de la implementación y de un marco


comparativo de dos algoritmos paralelos de multiplicación de matrices, el Algoritmo de
Cannon y Multiplicación de matrices por particionamiento por bloque circular, ambos
algoritmos se desarrollaron utilizando la librería MPI.
Palabras Claves: Procesos, transpaso de mensajes, nodos, número de nodos.

1 Introducción

En este trabajo se tiene por objetivo implementar y comparar dos versiones de algoritmos de
multiplicación de matrices con diferentes mecanismos de mapeo y particionamiento de tareas, en
particular los algoritmos de cannon y de multiplicación con mapeo por bloque circular. Además
se desea comparar el comportamiento de los mismos con diferentes cargas de trabajo y comparar
su desempeño con la versión mono-proceso mas comúnmente utilizada.
Para el desarrollo de las versiones en paralelo se ha utilizado la librería de paso de mensajes
MPICH con el compilador gcc.
A continuación se describe cada una de las implementaciones desarrolladas.
2 Descripción del Hardware y de la Red utilizada

Características de la Computadora1 Características de la Computadora2


Procesador: AMD Athlon64 x2 1.9Ghz Procesador: Pentium 4 x2 3Ghz
Memoria: 2GB RAM Memoria: 3GB RAM
Disco Duro: 160GB HD SATA Disco Duro: 250GB HD SATA
S.O: Windows XP Pro. SP2 S.O: Windows XP Pro. SP2
Velocidad de Tarjeta de Red: 1Gbps Velocidad de Tarjeta de Red: 1Gbps

Características de la Red
Velocidad de Transmisión: 1Gbps
Cable de Red: Giganet 5E UTP 4x2x2

3 Algoritmos Paralelos de Multiplicación de Matrices

3.1 Algoritmo de Cannon

Descripción
Este algoritmo es particularmente apropiado para sistemas de paso de mensajes, además de
ajustarse de manera muy sencilla al mapeo por bloques
Utiliza una malla con conexiones entre los elementos de cada lado (toro) para desplazar los
elementos de A hacia la izquierda y los de B hacia arriba.
El algoritmo sigue los siguientes pasos:
1. El procesador Pij tiene los elementos aij y bij.
2. La fila i-ésima de A se desplaza i posiciones a la izquierda, y la columna j-ésima de B se
desplaza j posiciones hacia arriba, ambos desplazamientos se realizan de forma circular.
3. Cada procesador multiplica su par de elementos.
4. Cada fila de A se desplaza una posición a la izquierda, y cada columna de B una posición
hacia arriba.
5. Cada procesador multiplica su nuevo par de elementos, y suma el resultado al anterior.
6. Se repiten los pasos 4 y 5 hasta terminar, es decir n – 1 desplazamientos.

Esta implementación recibe como entrada dos matrices cuadradas de dimensiones NxN y el
mapeo utilizado fue el de bloques de una dimensión (1D), de la siguiente forma. Se distribuyen
las filas de ambas matrices de entrada A y B entre cada uno de los P procesos, de modo que
cada proceso posee N/P filas de A y de B. Antes de asignar las filas a cada proceso, el proceso
P0 se encarga de realizar la preparación inicial de ambas matrices (paso 2 del algoritmo descrito
anteriormente).
De este modo, cada proceso procede a multiplicar y acumular los productos de los elementos de
A y B.
Para efectuar el paso 6 del algoritmo descrito, cada proceso efectúa una rotación con su copia
local de las filas contiguas de A y una rotación con su copia de las filas de la matriz B mediante
paso de mensajes, de modo que el proceso Pi envía una celda de su porción de B al proceso Pi-1
como se muestra en la siguiente figura:
Implementación

void multiplicar_matriz_cannon(MPI_Comm comm) {


int i, j, k, rank, size, M_cnt, row_end_cond, row_cnt;
int** mat_A;
int** mat_B;
int** mat_c;

MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Status stat;

row_end_cond = M/size;

if(rank == 0) {
row_cnt = M_cnt = M/size;
mat_A = crear_matriz (row_cnt, M);
mat_B = crear_matriz (row_cnt, M);
mat_c = crear_matriz (row_end_cond , M);
cannon_shift(matrizA, M, N, 'L');
cannon_shift(matrizB, M, N, 'U');

for(j=0 ; j<row_end_cond ; j++) {


mat_A[j] = matrizA[j];
mat_B[j] = matrizB[j];
}
M_cnt = row_end_cond;

for(i = 1 ; i<size ; i++) {


if(i==size-1 && M % (size)!=0) {
row_end_cond = M % (size)+ 1;
}

for(j=0 ; j<row_end_cond ; j++) {


MPI_Send(matrizA[M_cnt],N, MPI_INT, i, 1, comm);
MPI_Send(matrizB[M_cnt],N, MPI_INT, i, 2, comm);
M_cnt++;
if(j+i+M/size-1==M)
break;
}
}
row_end_cond = M/size;
}
else {
if(rank==size-1 && M % (size)!=0)
row_end_cond = M % (size)+ 1;

mat_A = crear_matriz (row_end_cond , M);


mat_B = crear_matriz (row_end_cond , M);
mat_c = crear_matriz (row_end_cond , M);
row_cnt = 0;

for(j=0 ; j<row_end_cond; j++) {


MPI_Recv(mat_A[j],N, MPI_INT, 0, 1, comm, &stat);
row_cnt++;
MPI_Recv(mat_B[j],N, MPI_INT, 0, 2, comm, &stat);
}
}

for(k=0 ; k<M; k++) {


for(i = 0 ; i<row_end_cond ; i++) {
for(j=0 ; j<M ; j++) {
mat_c[i][j] = mat_c[i][j] + mat_A[i][j]*mat_B[i][j] ;
}
shift(mat_A, 1, i, 'L' , row_end_cond, M);
}
MPI_shift(mat_B, row_end_cond, M, rank, size,comm);
}

if(rank==0) {
for(j=0 ; j<row_end_cond ; j++) {
matrizC[j] = mat_c[j];
}

M_cnt = row_end_cond;
for(i = 1 ; i<size ; i++) {
if(i==size-1 && M % (size)!=0) {
row_end_cond = M % (size)+ 1;
}

for(j=0 ; j<row_end_cond ; j++) {


MPI_Recv(matrizC[M_cnt],N, MPI_INT, i, 1, comm, &stat);
M_cnt++;
if(j+i+M/size-1==M)
break;
}
}
}
else {
row_cnt=0;
for(j=0 ; j<row_end_cond ; j++) {
MPI_Send(mat_c[j],N, MPI_INT, 0, 1, comm);
row_cnt++;
}
}
if(rank==0)
imprimir_matriz(matrizC, M, M);
}

void cannon_shift(int** array, int rows, int cols, char side)


{
int i =0;
int step;
int aux = array[0];
for(i = 0 ; i < rows ; i++) {
step = i;
shift(array, i,i, side, rows, cols);
}
}

void shift(int** array, int step, int row_shift, char side, int rows,int cols)
{
int k,j;
int i = step;//%(size-1);
int aux;
for(k=0;k<step;k++) {
if(side == 'L')
aux = array[row_shift][0];
else
aux = array[0][row_shift];

for(j =0 ; j<cols-1 ; j++) {


if(side == 'L')
array[row_shift][j] = array[row_shift][j+1];
else
array[j][row_shift] = array[j+1][row_shift];
}
if(side == 'L')
array[row_shift][cols-1] = aux;
else
array[cols-1][row_shift] = aux;
}
}

void MPI_shift(int** arr, int rows,int cols, int rank, int MPI_size, int comm) {
int stat;
int k,j;
int i = 0;
int aux=0;
int aux1=0;
int reciver;
int sender;

reciver = rank-1;
if(reciver<0)
reciver= MPI_size-1;
sender = (rank+1);
if(sender == MPI_size)
sender = 0;

for(i =0 ; i<cols ; i++) {


aux = arr[0][i];
aux1 = 0;
if(rank%2 == 0) {
MPI_Send (&aux,1, MPI_INT,reciver,i,comm);
MPI_Recv (&aux1,1, MPI_INT, sender,i,comm,&stat);
}
else {
MPI_Recv (&aux1,1, MPI_INT, sender,i,comm,&stat);
MPI_Send (&aux,1, MPI_INT,reciver,i,comm);
}

for(j =0 ; j<rows-1 ; j++) {


arr[j][i] = arr[j+1][i];
}
arr[rows-1][i] = aux1;
}
}

3.2 Multiplicación de matrices por particionamiento por bloque circular

Descripción

Una de las características de la distribución por particionamiento por bloque circular es que se
reduce la cantidad de ociosidad, todos los procesos tienen una muestra de tareas desde todas las
partes de la matriz. Como resultado, aunque si las partes diferentes de la matriz requieren
diferentes cantidades de trabajo, el trabajo general sobre cada proceso se balancea. También, ya
que las tareas asignadas a un proceso pertenecen a diferentes partes de la matriz, hay una buena
oportunidad para que al menos algunos de ellos estén listos para ejecutarse en un tiempo dado.

Note que si nosotros incrementamos el tamaño del bloque en el proceso de multiplicación de


matrices, los procesos realizarían más multiplicaciones por lo que el tiempo de ejecución se
disminuiría debido a que habría menos tiempo gastado en la comunicación de los procesos.

Un proceso MASTER (rank = 1) distribuye los bloques a los procesos 2 al p (p = a la cantidad de


procesos) y un proceso DESTINO (rank = 0) es el que recibe los resultados de las
multiplicaciones de los procesos 2 al p.

La distribución de los bloques de la matriz se realiza de la siguiente forma a cada proceso.


Implementación

/**
Existe 2 Procesos Especiales que tienen funciones específicas, y son las siguientes:
1- Un Proceso MASTER que tiene rank igual a 1, que tiene la función de distribuir los bloques para
cada proceso utilizando la técnica de distribución circular.
2- Otro proceso DESTINO que tiene rank igual a 0, que recibe todos los resultado de los procesos a
las cuales se les distribuyo los bloques para que pueda realizar las multiplicaciones.
Los demás procesos reciben sus bloques desde el proceso MASTER y realizan las multiplicaciones debidas con
sus bloques asignados y retornan sus resultados al proceso DESTINO con el fin de que pueda agrupar en una
matriz RESULTADO_TOTAL.

*/
int block_cyclic_matrix_multiplication (
int A [ROW_SIZE_MATRIX_A][COLUMN_SIZE_MATRIX_A_ROW_SIZE_MATRIX_B],
int B [COLUMN_SIZE_MATRIX_A_ROW_SIZE_MATRIX_B][COLUMN_SIZE_MATRIX_B],
int C[ROW_SIZE_MATRIX_A][COLUMN_SIZE_MATRIX_B],
MPI_Datatype datatype, int source, MPI_Comm comm) {

int rank_actual, numtasks;


MPI_Comm_rank(comm, &rank_actual); // Obtiene el rank actual
MPI_Comm_size(comm, &numtasks); // Obtiene el nro. de tareas

MPI_Status stat;
int rs, rr, tag=0;

if(rank_actual != DESTINO && rank_actual != MASTER ) {

int cyclic = 0;
for(cyclic = 0; cyclic <(ROW_SIZE_MATRIX_A/(PROCESS_AMOUNT * BLOCK_SIZE)); cyclic++) {
int filaInicial = 0;
rr = MPI_Recv(&filaInicial, 1, MPI_INT, MASTER, 0, comm, &stat);

printf("Proceso Nro.: %d, Fila Nro.: %d \n", rank_actual, filaInicial);


multiplication(A,B,C, filaInicial, BLOCK_SIZE);
}

rs = MPI_Send(C, BUFFER_SIZE, MPI_INT, DESTINO, tag, comm);


}
else if(rank_actual == DESTINO) {

int cyclic = 0;
for(cyclic = 0; cyclic <(ROW_SIZE_MATRIX_A/(PROCESS_AMOUNT * BLOCK_SIZE)); cyclic++) {
int source = 2;
for( source = 2; source<PROCESS_AMOUNT+2; source++ ) {
rr = MPI_Recv(RESULTADO_PARCIAL, BUFFER_SIZE, MPI_INT, source, 0, comm, &stat);

int i=0;
int j=0;
for(i=0; i<ROW_SIZE_MATRIX_A; i++){
for(j=0; j<COLUMN_SIZE_MATRIX_B; j++){
if(RESULTADO_PARCIAL[i][j] != 0) {
RESULTADO_TOTAL[i][j]=RESULTADO_PARCIAL[i][j];
}
}
}
}
}
}
else if(rank_actual == MASTER) {

int cyclic = 0;
for(cyclic = 0; cyclic <(ROW_SIZE_MATRIX_A/(PROCESS_AMOUNT * BLOCK_SIZE)); cyclic++) {

int destinatario = 0;
int *inicio = (int *)malloc(sizeof(int));
for(destinatario = 2; destinatario < PROCESS_AMOUNT+2; destinatario++) {

if(destinatario == 2) {
inicio[0] = cyclic*BLOCK_SIZE*PROCESS_AMOUNT;
}
else {
inicio[0] = inicio[0] + BLOCK_SIZE;
}

rs = MPI_Send(inicio, 1, MPI_INT, destinatario, tag, comm);

printf("Cyclic: %d Bloque: %d Destino: %d\n", cyclic, inicio[0], destinatario);


}
}
}
return 0;
}
4 Marco Comparativo

A continuación se muestra la tabla de resultados de los tiempos de ejecución para cada algoritmo
con diferentes cargas de trabajo:

Dimensión Número de Algoritmos Paralelos


de las Matrices Procesos Cannon
Cuadradas A y B (p) Tp To (p*Tp-Ts) S (Ts / Tp) E (S/p) C (p*Tp)
64x64 2 0,750 1,499 0,001 0,001 1,500
32 3,281 104,991 0,000 0,000 104,992
64 6,937 443,967 0,000 0,000 443,968
128x128 2 2,781 5,515 0,017 0,008 5,562
32 12,219 390,961 0,004 0,000 391,008
64 20,500 1311,953 0,002 0,000 1312,000
128 51,376 6576,081 0,001 0,000 6576,128
256x256 2 11,891 23,438 0,029 0,014 23,782
32 39,516 1264,168 0,009 0,000 1264,512
64 97,892 6264,744 0,004 0,000 6265,088
128 201,565 25799,976 0,002 0,000 25800,320
256 NOWORK
512x512 2 50,673 97,940 0,067 0,034 101,346
32 164,720 5267,634 0,021 0,001 5271,040
64 417,765 26733,554 0,008 0,000 26736,960
128 NOWORK

Dimensión Número de Algoritmos Paralelos


de las Matrices Procesos Block Cyclic
Cuadradas A y B (p) Tp To (p*Tp-Ts) S (Ts / Tp) E (S/p) C (p*Tp)
64x64 2 0,156 0,3109 0,007051282 0,0452005 0,312
32 1,297 41,5029 0,000848111 0,0006539 41,504
64 2,657 170,0469 0,000414001 0,0001558 170,048
128x128 2 0,390 0,733 0,120512821 0,3090072 0,78
32 4,422 141,457 0,010628675 0,0024036 141,504
64 8,453 540,945 0,005560156 0,0006578 540,992
128 17,156 2195,921 0,002739566 0,0001597 2195,968
256x256 2 1,000 1,656 0,344 0,344 2
32 3,032 96,68 0,113456464 0,0374197 97,024
64 36,031 2305,64 0,009547334 0,000265 2305,984
128 NOWORK
256 NOWORK
512x512 2 6,453 9,5 0,527816519 0,081794 12,906
32 14,422 458,098 0,236166967 0,0163755 461,504
64 NOWORK
128 NOWORK
Dimension Algoritmo
de las Matrices Serial
Cuadradas A y B Ts
64x64 0,001
128x128 0,047
256x256 0,344
512x512 3,406

Las matrices utilizadas para cada una de las comparaciones fueron las mismas, con valores
generados aleatoriamente.

En la tabla anterior los resultados de las pruebas fueron realizadas con matrices cuadradas de
dimensiones especificadas en la misma. Los valores “NOWORK” implican que la aplicación no
ha podido efectuar el cómputo con los parámetros especificados.

En la tabla de resultados, se observa claramente una gran diferencia entre los tiempos de
ejecución de los algoritmos paralelos y el algoritmo serial, siendo los tiempos de ejecución del
algoritmo serial sensiblemente menor que los de sus contrapartes paralelas, esto es debido a que
existe un sobrecosto elevado en la ejecución de los algoritmos paralelos utilizando la librería
MPI, ya que la misma efectúa una serie de operaciones e inicialización de variables externas al
algoritmo propiamente dicho que incrementan enormemente el overhead del mismo. Esto
sumado al costo de gestión de los diferentes procesos lanzados en las versiones paralelas así
como el costo de las comunicaciones entre procesos a través de la red, tanto en término de
cómputo como de tiempo de transmisión.

También se observa una diferencia entre el algoritmo de Cannon y el de Particionamiento por


bloques circulares, esto se debe a la mayor cantidad de interacciones inter procesos en la
implementación de Cannon que en la otra.
5 Conclusiones

Con este trabajo pudimos visualizar las diferencias en tiempo de ejecución entre los algoritmos
paralelos de multiplicación de matrices (Cannon y Distribución por Bloque Circular) y el
algoritmo serial de multiplicación de matrices, el tiempo de ejecución de los algoritmos paralelos
tienen un tiempo de sobrecarga asociada, el tiempo de comunicación interprocesos y las
comunicaciones a través de la red.

También se pudo observar una diferencia entre el algoritmo de Cannon y el de Distribución


cíclica de bloques, esto se debe a la mayor cantidad de interacciones inter-procesos en la
implementación de Cannon que en la otra. Por lo tanto, en las métricas de comparación (Tiempo
de Ejecución, Tiempo de Sobrecarga, Aceleración, Eficiencia y Costo) se puede observar que
comparando los algoritmos paralelos de Multiplicación de Matrices, el algoritmo de Cannon por
tener mayor interacción inter-proceso tendría peor valores de las métricas que el Algoritmo de
Particionamiento por Bloques Cíclicos.

Con este trabajo también se puede concluir que los Algoritmos Paralelos tienden a administrar
mejor los recursos como procesadores multi-núcleos.

6 Referencias

[1] Introduction to Parallel Computing, Second Edition, Capítulos: 3 y 5


By Ananth Grama, Anshul Gupta, George Karypis, Vipin Kumar
[2] Introduction to algorithms (2ed, MIT, 2001)(T)(C)
By Cormen, Leiserson, Rivest, Stein
INDICE
Introducción ................................................................................................................................... 1

Descripción del Hardware y de la Red utilizada ............................................................................ 2

Algoritmos Paralelos de Multiplicación de Matrices ..................................................................... 2

Algoritmo de Cannon ..................................................................................................................... 2

Multiplicación de matrices por particionamiento por bloque circular ........................................... 5

Marco Comparativo ....................................................................................................................... 7

Conclusiones .................................................................................................................................. 9

Referencias..................................................................................................................................... 9

Você também pode gostar