Algoritmos y Programación Paralela 1
Algoritmos Numéricos
Algoritmos matriciales básicos
Almacenamiento de matrices
Operaciones básicas con vectores
Operaciones básicas con matrices
Multiplicación de matrices
Factorización LU
Operaciones con matrices dispersas
Trabajo por bloques
Almacenamiento de matrices densas
En array bidimensional: double a[n][m]
En array unidimensional: double *b[n*m]
Fila i columna j:
a[n][m] , b[i*m+j]
Cuando es submatriz de otra
“ Leading dimension” : posiciones de memoria entre dos elementos consecutivos de la misma columna
c m
nn ld
Almacenamiento de matrices dispersas
Muchas maneras distintas
Tres arrays:
datos[0,..,d-1], filas[0,..,d-1], columnas[0,..,d-1]
datos (1,3,2,4), filas (0,1,2,2), columnas (2,0,1,3)
Tres arrays:
datos[0,..,d-1], columnas[0,..,d-1], comienzo fila[0,..,n-1]
datos (1,3,2,4), columnas (2,0,1,3), com. filas (0,1,2,-1)
0 1 2 3 0 1 1 3
2 2 4 3
Operaciones básicas con vectores
void escalar_vector(double d,double *v,int n) {
int i;
for(i=0;i<n;i++) v[i]*=d;
}
n flops (operaciones en coma flotante)
En algoritmos numéricos se estudia el número de flops.
En el estudio experimental los flops (Mflops, Gflops) por segundo.
Operaciones básicas con vectores
double sumar_vector(double *v,int n) {
int i;
double s=0.;
for(i=0;i<n;i++) s+=v[i];
return s;
}
n flops
Operaciones básicas con vectores
void sumar_vectores(double *v1,double *v2, double *vr,int n)
{
int i;
for(i=0;i<n;i++) vr[i]=v1[i]+v2[i];
}
n flops
Operaciones básicas con vectores
double producto_escalar(double *v1,double *v2,int n) {
int i;
double p=0.;
for(i=0;i<n;i++) p+=v1[i]*v2[i];
return p;
}
2n flops
Operaciones básicas con matrices
void escalar_matriz(double d,double **a,int n,int m) {
int i,j;
for(i=0;i<n;i++) for(j=0;j<m;j++) (a[i][j])*=d;
}
nm , n2 flops
Operaciones básicas con matrices
Uso de “ stride” de un vector
void producto_escalar_stride(double *v1,int str1,
double *v2,int str2,int n) {
int i;
double p=0.;
for(i=0;i<n;i++)
p+=v1[i*str1]*v2[i*str2];
return p;
}
2n flops
Operaciones básicas con matrices
void sumar_matrices(double *a,int fa,int ca,int lda, double *b,int fb,int cb,int ldb, double *c,int fc,int cc,int ldc) {
int i,j;
for(i=0;i<fa;i++) for(j=0;j<ca;j++)
c[i*ldc+j]=a[i*lda+j]+b[i*ldb+j];
n2 flops
Operaciones básicas con matrices
void matriz_vector(double *m,int fm,int cm,int ldm,
double *v,int fv,int strv,double *r,int fr,int strr) { int i,j;
double s;
for(i=0;i<fm;i++) { s=0.;
for(j=0;j<cm;j++)
s+=m[i*ldm+j]*v[j*strv];
r[i*strr]=s;
} }
2n flops 2n2 flops
Operaciones básicas con matrices
void matriz_vector_pe(double *m,int fm,int cm,int ldm,double *v,int fv,int strv,double *r,int fr,int strr) {
int i,j;
double s;
for(i=0;i<fm;i++)
r[i*strr]=producto_escalar_stride(&m[i*ldm],1,v,1,cm);
}
2n flops 2n2 flops
Operaciones básicas con matrices
void trasponer_matriz(double *m,int n,int ld) {
int i,j;
double t;
for(i=0;i<n;i++)
for(j=i+1;j<n;j++) { t=m[i*ld+j];
m[i*ld+j]=m[j*ld+i];
m[j*ld+i]=t;
} }
3(ni) asignaciones 3n(n+1)/2 asignaciones
Multiplicación de matrices
i i
j j
k k
Multiplicación de matrices
void matriz_matriz(double **a,int fa,int ca,double **b, int fb,int cb,double **c,int fc,int cc)
{ int i,j,k; double s;
for(i=0;i<fa;i++) { for(j=0;j<cb;j++) { s=0.;
for(k=0;k<ca;k++) { s+=a[i][k]*b[k][j];
}
c[i][j]=s;
} } }
2n flops 2n2 flops
2n3 flops
Multiplicación de matrices
void matriz_matriz_ld(double *a,int fa,int ca,int lda,
double *b,int fb,int cb,int ldb,double *c,int fc,int cc,int ldc) { int i,j,k;
double s;
for(i=0;i<fa;i++) { for(j=0;j<cb;j++) { s=0.;
for(k=0;k<ca;k++)
s+=a[i*lda+k]*b[k*ldb+j];
c[i*ldc+j]=s;
2n flops 2n2 flops
2n3 flops
Multiplicación de matrices
void matriz_matriz_tras(double *a,int fa,int ca,int lda,
double *b,int fb,int cb,int ldb,double *c,int fc,int cc,int ldc) { int i,j,k; double s; double *bt,*da,*db;
bt=(double *) malloc(sizeof(double)*cb*fb);
trasponer_matriz_esp(b,fb,cb,ldb,bt,cb,fb,fb);
for(i=0;i<fa;i++) { for(j=0;j<cb;j++) {
s=0.; da=&a[i*lda]; db=&bt[j*fb];
for(k=0;k<ca;k++,da++,db++) s+=da[0]*db[0];
c[i*ldc+j]=s;
} } free(bt); }
2n flops 2n2 flops
2n3 flops
Multiplicación de matrices
void matriz_matriz_pe(double *a,int fa,int ca,int lda,
double *b,int fb,int cb,int ldb,double *c,int fc,int cc,int ldc) {
int i,j;
for(i=0;i<fa;i++) for(j=0;j<cb;j++)
c[i*ldc+j]=producto_escalar_stride(&a[i*lda],1,
&b[j],ldb,ca);
2n flops 2n2 flops
2n3 flops
Multiplicación de matrices
void matriz_matriz_mv(double *a,int fa,int ca,int lda,double
*b,int fb,int cb,int ldb,double *c,int fc,int cc,int ldc) {
int i;
for(i=0;i<cb;i++)
matriz_vector_pe(a,fa,ca,lda,&b[i],fb,ldb,&c[i],fc,ldc);
}
2n2 flops 2n3 flops
Multiplicación de matrices
en mi portátil
:
Método\tamaño 1000 1200 1400
bidimensional 12.26 20.15 32.37
leading dimension12.70 21.95 36.41
leading+punteros 12.29 22.84 34.90
traspuesta 12.71 20.88 36.29
producto escalar 12.92 21.75 35.17
matriz-vector 12.19 21.47 36.92
Multiplicación de matrices
en SUN Ultra 1
:
Método\tamaño 200 400 800
Normal 0.2179 13.4601 217.5464
Traspuesta 0.2013 3.3653 27.9945
Bloques 10 0.2880 2.5901 21.9029
25 0.2192 1.8347 14.9642
50 0.2161 1.7709 14.2502
Bloq tras 10 0.2937 2.5026 20.4405
25 0.2195 1.8009 14.6415
50 0.2152 1.7628 14.1806
Almac blo 10 0.2949 2.5122 20.3762
25 0.2277 1.8490 14.8625
50 0.2296 1.8429 14.7314
Bl tr al bl 10 0.2925 2.4985 20.1975
25 0.2244 1.8082 14.5282
50 0.2231 1.7147 13.6553
Bloq dob 20 5 0.6105 4.9363 39.9594
20 10 0.3206 2.6669 19.7044
50 10 0.3039 2.4542 19.7044
50 25 0.2370 1.9221 15.5190
Multiplicación de matrices
en kefren, pentium 4
:
Método\tamaño 200 400 800
Normal 0.0463 0.7854 7.9686
Traspuesta 0.0231 0.2875 2.3190
Bloques 10 0.0255 0.2493 2.0327
25 0.0265 0.2033 1.6928
50 0.0219 0.1785 1.6594
Bloq dob 20 5 0.0393 0.3669 3.4955
20 10 0.0269 0.3090 2.4424
50 10 0.0316 0.2232 2.2768
50 25 0.0215 0.1755 1.4726
Blas 1 0.0536 0.8190 8.2311
Blas 2 0.0501 0.5861 5.9997
Factorización LU
Uii=1
Paso 1: L00 U00=A00 L00 =A00 Paso 2: L00 U0i=A0i U0i =A0i/L00 Li0 =Ai0
Paso 3: Aij =Li0 U0j +... A’ ij =Aij -Li0 U0j
Factorización LU
Uii=1
Paso 1: Lii Uii=Aii Lii =Aii Paso 2: Lii Uij=Aij Uij =Aij/Lii Lji =Aji
Paso 3: A =L U +... A’ =A -L U
i
i
L
U
Factorización LU
void lu(double *a,int fa,int ca,int lda) { int i,j,k;
for(i=0;i<fa;i++) {
for(j=i+1;j<ca;j++) //Paso 2 a[i*lda+j]/=a[i*lda+i];
for(j=i+1;j<fa;j++) //Paso 3 for(k=i+1;k<ca;k++)
a[j*lda+k]-=a[j*lda+i]*a[i*lda+k];
} }
Factorización LU
Para resolver sistema Ax=b
En la forma: LUx=b
Resolver: Ly=b (Sistema triangular inferior: sustitución progresiva)
Seguido de: Ux=y (Sistema triangular
superior: sustitución regresiva)
Operaciones con matrices dispersas
Producto matriz-vector
Por cada elemento de la matriz
Buscamos si hay elemento de la misma columna en el vector y acumulamos sobre la suma
correspondiente a su fila
datos 3 1 2 4 fila 0 1 2 2 columna 2 0 2 3
datos 1 3 fila 0 2
resultado datos 9 1 6 fila 0 1 2
Operaciones con matrices dispersas
void matriz_vector_disperso(double *dm,int *fm,int *cm, int ndm,double *dv,int *fv,int ndv,double *dr,int *fr,int ndr) { int i,j,fact; double s;
for(i=0;i<ndr;i++) fr[i]=i;
i=0;
while(i<ndm) { fact=fm[i]; j=0; s=0.;
while(i<ndm && fm[i]==fact) { while(j<ndv && fv[j]<cm[i]) j++;
if(j<ndv && fv[j]==cm[i]) s+=dm[i]*dv[j];
dr[fact]=s; i++;
Trabajo por bloques
En las operaciones anteriores los costes son:
Vectorvector
Matrizvector
Matrizmatriz
Desarrollando algoritmos con operaciones matrizmatriz, para el mismo número de operaciones aritméticas menos accesos a memoria ⇒ menor tiempo de ejecución
Especialmente adecuado para jerarquías de memoria amplias (Memoria Compartida)
Usado en el desarrollo de librerías desde los 80 (LAPACK)
Computacional Memoria
n n
n
2n
2n
3n
2Multiplicación de matrices
en mi portátil
:
Método\tamaño 1000 1200 1400
normal 12.70 21.95 36.41
bloques 25 3.69 6.30 9.25
50 3.56 5.90 8.71
100 4.25 6.33 8.95
bloques 25 5 4.67 7.87 10.89
dobles 50 10 5.03 8.08 12.93
50 25 4.53 7.16 11.11
100 20 4.87 7.33 10.97
100 25 4.78 7.06 9.92
Reducción 76%
Trabajo por bloques
Multiplicación de matrices, en SUN Ultra 1
:
Método\tamaño 200 400 800
Normal 0.2179 13.4601 217.5464
Traspuesta 0.2013 3.3653 27.9945
Bloques 10 0.2880 2.5901 21.9029
25 0.2192 1.8347 14.9642
50 0.2161 1.7709 14.2502
Bloq tras 10 0.2937 2.5026 20.4405
25 0.2195 1.8009 14.6415
50 0.2152 1.7628 14.1806
Almac blo 10 0.2949 2.5122 20.3762
25 0.2277 1.8490 14.8625
50 0.2296 1.8429 14.7314
Bl tr al bl 10 0.2925 2.4985 20.1975
25 0.2244 1.8082 14.5282
50 0.2231 1.7147 13.6553
Bloq dob 20 5 0.6105 4.9363 39.9594
20 10 0.3206 2.6669 19.7044
50 10 0.3039 2.4542 19.7044
50 25 0.2370 1.9221 15.5190
Reducción 93%
Trabajo por bloques
Multiplicación de matrices, en kefren, pentium 4
:
Método\tamaño 200 400 800
Normal 0.0463 0.7854 7.9686
Traspuesta 0.0231 0.2875 2.3190
Bloques 10 0.0255 0.2493 2.0327
25 0.0265 0.2033 1.6928
50 0.0219 0.1785 1.6594
Bloq dob 20 5 0.0393 0.3669 3.4955
20 10 0.0269 0.3090 2.4424
50 10 0.0316 0.2232 2.2768
50 25 0.0215 0.1755 1.4726
Reducción 79%
Trabajo por bloques
La reducción varía de un sistema a otro
¿Cómo se sabe el tamaño de bloque óptimo? Varía con:
Sistema
Tamaño del problema
Con lo que el método preferido también varía con el tamaño y el sistema
Se pueden hacer otras combinaciones que en algunos casos
dan mejores resultados. Pero difícil determinar mejor método
y parámetros
Multiplicación de matrices
A
i k
tb B
k
tb j C
i
tb j
s
Multiplicación de matrices
void matriz_matriz_ld(double *a,int fa,int ca,int lda,
double *b,int fb,int cb,int ldb,double *c,int fc,int cc,int ldc) { int i,j,k;
double s;
for(i=0;i<fa;i++) { for(j=0;j<cb;j++) { s=0.;
for(k=0;k<ca;k++)
s+=a[i*lda+k]*b[k*ldb+j];
c[i*ldc+j]=s;
} } }
Algoritmo sin bloques (normal).
Acceso elemento a elemento.
Problemas pequeños buenas prestaciones pues caben en memoria de niveles bajos de la jerarquía.
Problemas grandes peores prestaciones.
Multiplicación de matrices
void matriz_matriz_bloques(double *a,int fa,int ca,int lda,double *b,int fb, int cb,int ldb,double *c,int fc,int cc,int ldc,int tb)
{ int i,j,k; double *s;
s=(double *) malloc(sizeof(double)* tb * tb);
for(i=0;i<fa;i=i+ tb) for(j=0;j<cb;j=j+ tb) { ceros(s, tb, tb, tb);
for(k=0;k<ca;k=k+ tb)
multsumar(&a[i*lda+k], tb, tb,lda,&b[k*ldb+j], tb, tb,ldb,s, tb, tb, tb);
copiar(s, tb, tb, tb,&c[i*ldc+j], tb, tb,ldc);
} free(s);
Algoritmo por bloques.
Acceso y operaciones por bloques.
Buenas prestaciones
independiente del tamaño.
El tamaño de bloque es parámetro a determinar.
Algoritmos y Programación Paralela 37
Multiplicación de matrices
void matriz_matriz_bloquesdobles(double *a,int fa,int ca,int lda,double *b, int fb,int cb,int ldb,double *c,int fc,int cc,int ldc,int tb,int tbp)
{ int i,j,k; double *s;
s=(double *) malloc(sizeof(double)* tb * tb);
for(i=0;i<fa;i=i+ tb) for(j=0;j<cb;j=j+ tb) { ceros(s, tb, tb, tb);
for(k=0;k<ca;k=k+ tb)
multsumarbloques(&a[i*lda+k], tb, tb,lda,&b[k*ldb+j], tb, tb,ldb, s, tb, tb, tb, tbp);
copiar(s, tb, tb, tb,&c[i*ldc+j], tb, tb,ldc);
}
free(s);
}
Algoritmo por bloques dobles.
La operación sobre bloques no es la multiplicación
directa, sino por bloques.
Tenemos dos tamaños de bloque.
Multiplicación de matrices
Almacenamiento por bloques:
matriz
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
almacenamiento 0 1 4 5 2 3 6 7 8 9 12 13 10 11 14 15
posible acceso más rápido a los datos dentro de las operaciones por bloques
Factorización LU
Cada Aij, Lij, Uij de tamaño b×b
Paso 1: L00 U00=A00 Factorización sin bloques
Paso 2: L00 U01=A01 Sistema múltiple triangular inferior (¿bloques?) Paso 3: L10 U00=A10 Sistema múltiple triangular superior (¿bloques?) Paso 4: A11 =L10 U01 + L11 U11 A’ 11 =A11 L10 U01 , por bloques
y seguir trabajando con el nuevo valor de A11
Factorización LU
void lu_bloques(double *a,int fa,int ca,int lda,int tb) {int i,j,k,f,c;
for(i=0;i<fa;i=i+tb) {
f=(tb<fai ? tb : fai); c=(tb<cai ? tb : cai);
lu(&a[i*lda+i],f,c,lda);
if(i+tb<fa) {
sistema_triangular_inferior(&a[i*lda+i],f,c,lda,&a[i*lda+i+c],f,caic,lda);
sistema_triangular_superior(&a[i*lda+i],f,c,lda,&a[(i+f)*lda+i],faif,c,lda);
multiplicar_restar_matrices(&a[(i+f)*lda+i],faif,c,lda,
&a[i*lda+i+f],f,caic,lda,&a[(i+f)*lda+i+c],faif,caic,lda);
} } }
Factorización LU
en mi portátil
:
tamaño bloque\matriz 800 1000
1 2.10 4.01
12 1.42 2.78
25 1.29 2.27
37 1.24 2.37
44 1.20 2.00
50 1.22 2.32
100 1.47 2.24
200 2.29 3.47
400 2.17 3.67
sin bloques 1.73 3.43