Sie sind auf Seite 1von 22

Mecanismos Bsicos de Optimizacin de Cdigo

En esta seccin se van a proponer distintos experimentos que tienen por objetivo ilustrar algunos de los mecanismos bsicos de optimizacin que incluyen los modernos compiladores de hoy en da. En cada uno de los apartados que siguen a continuacin donde se describen las optimizaciones bsicas aparecen varios campos:

un ttulo: OPTIMIZACIN, que indica el tipo de optimizacin de que se trata. un cdigo de partida: ANTES, que representa un cdigo sin optimizar. un cdigo final: DESPUS, que representa el cdigo resultante de aplicar la optimizacin indicada en el ttulo.

El objetivo de esta prctica es que se implementen, para cada uno de los apartados que siguen, los cdigos ANTES y DESPUS, y se midan los tiempos de ejecucin (CPU time, incluyendo sistema y usuario). Bsicamente, las pasos que se han de seguir para realizar la(s) prctica(s) son los siguientes: Pasos para la realizacin de la prctica:

1.- Para cada una de las Optimizaciones Bsicas que se relacionan ms abajo, se ha de desarrollar un programa que siga la siguiente estructura general:

double utime0, stime0, wtime0, utime1, stime1, wtime1, utime2, stime2, wtime2; /* Establecimiento del instante inicial de medicin de tiempo. */ uswtime(&utime0, &stime0, &wtime0); /* Cdigo ANTES. */ ... ... ... /* Instante final del tiempo de ejecucin del cdigo ANTES. */ /* Establecimiento del instante inicial del medicin de tiempo para cdigo

DESPUS. */ uswtime(&utime1, &stime1, &wtime1); /* Cdigo DESPUS. */ ... ... ... /* Instante final del tiempo de ejecucin del cdigo ANTES. */ uswtime(&utime2, &stime2, &wtime2); /* --- Clculo de los tiempos de ejecucin. --- */ /* Cdigo ANTES: */ printf("\nCdigo ANTES:\n"); printf("real %.3f\n", wtime1 - wtime0); printf("user %.3f\n", utime1 - utime0); printf("sys %.3f\n", stime1 - stime0); printf("\n"); printf("CPU/Wall %.3f %% \n", 100.0 * (utime1 - utime0 + stime1 - stime0) / (wtime1 - wtime0)); printf("\n"); /* Cdigo DESPUS: */ printf("\nCdigo DESPUS:\n"); printf("real %.3f\n", wtime2 - wtime1); printf("user %.3f\n", utime2 - utime1); printf("sys %.3f\n", stime2 - stime1); printf("\n"); printf("CPU/Wall %.3f %% \n", 100.0 * (utime2 - utime1 + stime2 - stime1) / (wtime2 - wtime1)); printf("\n"); /* Factor de Ganancia */ printf("\nFactor de Ganancia:\n"); printf("CPU %.2f \n", (utime1 - utime0 + stime1 - stime0) / (utime2 utime1 + stime2 - stime1)); printf("Wall %.2f \n", (wtime1 - wtime0) / (wtime2 - wtime1)); printf("\n");

2.- Compilacin del programa con el compilador gcc, sin emplear ninguna opcin de optimizacin: gcc -o programa programa.c -lm

3.- A continuacin se procede a la ejecucin del programa, obteniendo los tiempos de ejecucin (CPU time, el conjunto de los tiempos de usuario y sistema) de los segmentos de cdigo ANTES y DESPUS, as como el factor de ganancia. 4.- Compilacin del programa con el compilador gcc, empleando optimizacin del compilador: gcc -o programa programa.c -lm -O2

5.- Ejecucin del programa, obteniendo los tiempos de ejecucin (CPU time, el conjunto de los tiempos de usuario y sistema) de los segmentos de cdigo ANTES y DESPUS, as como el factor de ganancia. 6.- Repetir los pasos anteriores para varios valores de N (tamao del problema): potencias de 2 hasta llegar a un mximo de la la memoria RAM del computador. El objetivo es realizar una grfica de los factores de ganancia en funcin del tamao del problema. El valor de N se puede establecer como una constante definida en el programa:

#define N 20971520

Comentarios: En condiciones normales, los segmentos de cdigo DESPUS han de mostrar tiempos de ejecucin ms bajos que los correspondientes cdigos ANTES. El alumno ha de reflexionar y pensar en las posibles causas por las cuales se consigue ese ahorro en tiempo de ejecucin, intentando relacionarlo con los conceptos vistos en clase de teora. Por ejemplo, en el caso de la optimizacin por intercambio de bucles, la razn por la cual se gana de forma sustancial en tiempo de ejecucin es porque se logra acceso a memoria por stride unidad. Otras razones que se pueden dar es por la sustitucin de determinadas operaciones complejas por operaciones ms simples (p.ej. la divisin en punto flotante se sustituye por la multiplicacin en punto flotante porque en la mayora de los computadores la multiplicacin es menos costosa y, adems, en algunos procesadores la unidad funcional de divisin ni siquiera est segmentada, con lo cual el throughput asociado es mnimo.

Para obtener un tiempo de computacin y un factor de ganancia significativo, los bucles se repiten ITER iteraciones, donde ITER es una constante definida como 100 al principio de los programas:

#define ITER 100


El tiempo de computacin mnimo para que sea significativo ha de ser de 1 segundo al menos.

Optimizaciones bsicas:
Los mecanismos bsicos de optimizacin que se han de implementar son los que se relacionan a continuacin:

pr1: OPTIMIZACIN: Eliminacin de expresiones comunes. ANTES: main() { int i, j; float a, b, c, d, k, x, y, z, t; static float A[N]; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ a = x * y * z * A[i]; b = x * z * t; c = t * A[i]; d = k * A[i]; A[i] = z * x * A[i]; } } DESPUS:

main() { int i, j; float a, b, c, d, k, x, y, z, t; static float A[N]; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ register float tmp1, tmp2, tmp3; tmp1 = x * z; tmp2 = A[i]; tmp3 = tmp1 * tmp2; a = tmp3 * y; b = tmp1 * t; c = t * tmp2; d = k * tmp2; A[i] = tmp3; } }

pr2: OPTIMIZACIN: Reemplazo de multiplicaciones/divisiones enteras por operaciones de desplazamiento. ANTES: main() { int i, j, a, b; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ a = i * 128; b = a / 32; } } DESPUS:

main() { int i, j, a, b; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ a = i << 7; b = a >> 5; } }

pr3: OPTIMIZACIN: Reemplazo de divisiones enteras de 32 bits por divisiones punto flotante de 64 bits. ANTES: main() { int i, j, a; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ a = i / 545; } } DESPUS: main() { int i, j, a; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ a = (int)((float)i / 545.0); } }

pr4: OPTIMIZACIN: Reemplazo de divisiones en punto flotante por una sola divisin y multiplicaciones ANTES: main() { int i, j; float a, b, c ,d, t, u, v, x, y, z, tmp; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ x = i+1.0; y = j+x; a = u / x; b = t / x; c = z / y; d = v /(x*y); } } DESPUS: main() { int i, j; float a, b, c ,d, t, u, v, x, y, z, tmp; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ x = i+1.0; y = j+x; tmp = 1/(x*y); a = u * y * tmp; b = t * y * tmp; c = z * x * tmp; d = v * tmp; }

pr5: OPTIMIZACIN: Pasar bucles dentro del procedimiento/funcin ANTES: void add(float x, float y, float *z) { *z = x + y; } ... main() { int i, j; static float x[N], y[N], z[N]; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ add(x[i], y[i], &z[i]); } } DESPUS: void add2(float *x, float *y, float *z) { register int i, j; for(j=0; j<ITER; j++) for(i=0; i<N; i++) z[i] = x[i] + y[i]; } ...

main() { static float x[N], y[N], z[N]; add2(x, y, z); }

pr6: OPTIMIZACIN: Inlining: Sustituir la llamada a una funcin por el propio cuerpo de la funcin. ANTES: void add(float x, float y, float *z) { *z = x + y; } ... main() { int i, j; static float x[N], y[N], z[N]; for(j=0; j<ITER; j++) for(i=0; i<N; i++){ add(x[i], y[i], &z[i]); } } DESPUS: main() { int i, j; static float x[N], y[N], z[N];

for(j=0; j<ITER; j++) for(i=0; i<N; i++) z[i] = x[i] * y[i]; }

pr7: OPTIMIZACIN: Fusin de bucles: Combinar los cuerpos de varios bucles en un solo bucle ANTES: main() { int i, j; static float

x[N], y[N], z[N], k[N], t[N], u[N];

for(j=0; j<ITER; j++){ for(i=0; i<N; i++) z[i] = x[i] + y[i]; for(i=0; i<N; i++) k[i] = x[i] - y[i]; for(i=0; i<N; i++) t[i] = x[i] * y[i]; for(i=0; i<N; i++) u[i] = z[i] + k[i] + t[i]; } } DESPUS: main() { int i, j; static float

x[N], y[N], z[N], k[N], t[N], u[N];

for(j=0; j<ITER; j++){ for(i=0; i<N; i++) { register float tmpx, tmpy; tmpx = x[i]; tmpy = y[i]; z[i] = tmpx + tmpy;

k[i] = tmpx - tmpy; t[i] = tmpx * tmpy; u[i] = z[i] + k[i] + t[i]; } } }

pr8: OPTIMIZACIN: Fusin de bucles (II) ANTES: main() { int i, j; typedef struct {float x, y, z;} nuevotipo; static nuevotipo v1[N], v2[N], v3[N]; for(j=0; j<ITER; j++){ for(i=0; i<N; i++) v3[i].x = v1[i].x + v2[i].x; for(i=0; i<N; i++) v3[i].y = v1[i].y - v2[i].y; for(i=0; i<N; i++) v3[i].z = v1[i].z * v2[i].z; } } DESPUS: main() { int i, j; typedef struct {float x, y, z;} nuevotipo; static nuevotipo v1[N], v2[N], v3[N]; for(j=0; j<ITER; j++){ for(i=0; i<N; i++) v3[i].x = v1[i].x v3[i].y = v1[i].y v3[i].z = v1[i].z { + v2[i].x; - v2[i].y; * v2[i].z;

} } }

pr9: OPTIMIZACIN: Intercambio de bucles: Intercambio de bucles para permitir acceso a memoria por stride. ANTES: main() { int i, j, k; static float

x[N][N], y[N][N];

for(k=0; k<ITER; k++){ for(i=0; i<N; i++) for(j=0; j<N; j++) y[j][i] = x[j][i] + 3.0; } } DESPUS: main() { int i, j, k; static float

x[N][N], y[N][N];

for(k=0; k<ITER; k++){ for(j=0; j<N; j++) for(i=0; i<N; i++) y[j][i] = x[j][i] + 3.0; } }

pr10: OPTIMIZACIN: Loop peeling.

ANTES: main() { int i, j, k; static float

x[N], y[N];

for(k=0; k<ITER; k++) for(i=0; i<N; i++) if(i==0) x[i] = 0; else if(i==N-1) x[i] = N-1; else x[i] = x[i]+y[i]; } DESPUS: main() { int i, j, k; static float

x[N], y[N];

for(k=0; k<ITER; k++) { x[0] = 0; x[N-1] = N-1; for(i=1; i<N-1; i++) x[i] = x[i]+y[i]; } }

pr11: OPTIMIZACIN: Test promotion. ANTES: main() { int i, j, k; static float a, x[N], y[N];

for(k=0; k<ITER; k++) for(i=0; i<N; i++) if(a==0.0) x[i] = 0; else y[i] = x[i]*y[i]; } DESPUS: main() { int i, j, k; static float

a, x[N], y[N];

for(k=0; k<ITER; k++) { if(a==0) { for(i=0; i<N; i++) x[i] = 0; } else { for(i=0; i<N; i++) y[i] = x[i]*y[i]; } } }

pr12: OPTIMIZACIN: Overhead debido a los if ANTES: main() { int i, j, k; static float x[2*N][2*N], z[2*N][2*N]; for(k=0; k<ITER; k++) for(j=-N; j<N; j++) for(i=-N; i<N; i++){ if(i*i+j*j != 0)

z[N+j][N+i] = x[N+j][N+i] / (i*i+j*j); else z[N+j][N+i] = 0.0F; } } DESPUS: main() { int i, j, k; static float x[2*N][2*N], z[2*N][2*N]; for(k=0; k<ITER; k++) for(j=-N; j<N; j++) { for(i=-N; i<0; i++) z[N+j][N+i] = x[N+j][N+i] / (i*i+j*j); z[N+j][N] = 0.0F; for(i=1; i<N; i++) z[N+j][N+i] = x[N+j][N+i] / (i*i+j*j); } }

pr13: OPTIMIZACIN: Unin balanceada de operaciones. ANTES: main() { int i, j, k; static float x[N], y[N]; for(k=0; k<ITER; k++){ for(i=0; i<N; i++) x[i] = sqrt((float)i); for(i=0; i<N; i++) y[i] = (float)i+2.0; } }

DESPUS: main() { int i, j, k; static float

x[N], y[N];

for(k=0; k<ITER; k++){ for(i=0; i<N; i++) { x[i] = sqrt((float)i); y[i] = (float)i+2.0; } } }

pr14: OPTIMIZACIN: Desenrolle de bucles internos ANTES: main() { int i, k; float a, b; static float x[N], y[N]; for(k=0; k<ITER; k++){ for(i=0; i<N; i++) y[i] = a * x[i] + b; } } DESPUS: main() { int i, k;

float a, b; static float x[N], y[N]; for(k=0; k<ITER; k++){ for(i=0; i<N; i+=4){ y[i] = a * x[i] y[i+1] = a * x[i+1] y[i+2] = a * x[i+2] y[i+3] = a * x[i+3] } } }

+ + + +

b; b; b; b;

pr15: OPTIMIZACIN: Desenrolle de bucles internos y optimizacin de reducciones ANTES: main() { int i, j, k; static float x[N], y[N]; register float a, a1, a2, a3; for(k=0; k<ITER; k++){ a=0.0; for(i=0; i<N; i++) a = a + x[i] * y[i]; } } DESPUS: main() { int i, j, k; static float x[N], y[N]; register float a, a1, a2, a3;

for(k=0; k<ITER; k++){ a = a1 = a2 = a3 = 0.0; for(i=0; i<N; i+=4){ a = a + x[i] * y[i]; a1 = a1 + x[i+1] * y[i+1]; a2 = a2 + x[i+2] * y[i+2]; a3 = a3 + x[i+3] * y[i+3]; } a = a + a1 + a2 + a3; } }

pr16: OPTIMIZACIN: Desenrolle de bucles internos y optimizacin de reducciones (2) ANTES: main() { int i, j, k; static int x[N]; register int a, a0, a1, a2, a3; for(k=0; k<ITER; k++){ a=1.0; for(i=0; i<N; i++) a = a * x[i]; } } DESPUS: main() { int i, j, k; static int x[N]; register int a, a0, a1, a2, a3;

for(k=0; k<ITER; k++){ a = 1.0; for(i=0; i<N; i+=4){ a0 = x[i]; a1 = x[i+1]; a2 = x[i+2]; a3 = x[i+3]; a = a * (a0*(a1*(a2*a3))); } } }

pr17: OPTIMIZACIN: Desenrolle de bucles internos y optimizacin de reducciones (3) ANTES: main() { int i, j, k; static int x[N]; register int a, a0, a1, a2, a3; for(k=0; k<ITER; k++){ a=1.0; for(i=0; i<N; i++) a *= x[i]; } } DESPUS: main() { int i, j, k; static int x[N];

register int a, a0, a1, a2, a3; for(k=0; k<ITER; k++){ a = a1 = a2 = a3 = 1.0; for(i=0; i<N; i+=4){ a *= x[i]; a1 *= x[i+1]; a2 *= x[i+2]; a3 *= x[i+3]; } a = a * (a1*(a2*a3)); } }

pr18: OPTIMIZACIN: Unroll and Jam ANTES: main() { register int i, j, k, l; static float a[N][N], b[N][N], c[N][N]; for(l=0; l<ITER; l++){ for(i=0; i<N; i++) for(j=0; j<N; j++) for(k=0; k<N; k++) c[k][i] += a[k][j] * b[j][i]; } } DESPUS: main() {

register int i, j, k, l; static float a[N][N], b[N][N], c[N][N]; for(l=0; l<ITER; l++){ for(i=0; i<N; i+=2) for(j=0; j<N; j+=2) for(k=0; k<N; k++){ c[k][i] += a[k][j] * b[j][i] + a[k][j+1] * b[j+1][i] ; c[k][i+1] += a[k][j] * b[j][i+1] + a[k][j+1] * b[j+1][i+1] ; } } }

pr19: OPTIMIZACIN: Reordenamiento de bucles para mejora de la localidad espacial en la multiplicacin de matrices. Considere el problema de la multiplicacin de 2 matrices de tamao NxN: C=A*B. El procedimiento que normalmente se utiliza para implementar esta multiplicacin normalmente consta de 3 bucles anidados con ndices i, j y k: for (i = 0; i < N; i++) for (j = 0; j < N; j++) { sum = 0.0; for (k = 0; k < N; k++) sum += A[i][k]*B[k][j]; C[i][j] += sum; } Es posible modificar el orden de los bucles y realizar pequeos cambios en el cdigo para mejorar la localidad espacial y mejorar el rendimiento. En esta prctica se proporciona un programa en C llamado mm.c que contiene 6 versiones funcionalmente equivalentes de este cdigo. Las 6 versiones estn denotadas por el orden de los ndices de los bucles: ijk, jik, jki, kji, kij, ikj. Para ver la implementacin exacta, vase el cdigo fuente mm.c Compile y ejecute el programa. El programa proporciona los tiempos de computacin para distintos valores de N. Realice un grfica con los tiempos de ejecucin en funcin del tamao del problema (N). Fjese en cules son los rdenes de los bucles que permiten ejecutar la multiplicacin de forma ms eficaz, y piense en cules pueden ser las razones por las que las distintas versiones funcionan de forma tan distinta.

http://vidatraslapc.blogspot.com/2010/01/doctor-house-en-espanol-latino-online_06.html