lunes, 26 de marzo de 2012

Comienza la práctica 3: Uso de profilers

Esta semana comenzamos la práctica 3. En esta práctica, vamos a partir de un código fuente que compilaremos usando las opciones de profiling para analizar en qué funciones se gasta más tiempo durante la ejecución de nuestro programa.

Como ejemplo, tomemos el siguiente programa C++ en el que tenemos dos funciones que se llaman desde el main:

 

#include <iostream>

using namespace std;

#define MAX 1000000

 

int a[MAX];

int i = 0;

 

void rellenar(int i) {

       //operaciones aritméticas incluidas para perder tiempo

       double x = 0.7;

       double y = (x*0.2+0.5)*(x-0.1);

       x = x*(x*0.2+0.5)*(x+0.1);

       y = y/x;

 

       if (i % 2 == 0){

              a[i] = (i / 2);

       }else{

              a[i] = (3 * i + 1);

       }

}

 

int sumar(void) {

       int suma = 0;

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

              suma = suma + a[i];

       }

       return suma;

}

 

int main() {

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

              rellenar(i);

       }

 

       int total = sumar();

 

       cout << total << endl;

 

       return 0;

}

 

Hay dos detalles que hacen muy ineficiente este programa: (1) poner el for en el main para llamar 1000000 de veces a rellenar supone un gasto de tiempo muy grande (las llamadas a subrutinas son operaciones muy costosas); (2) hemos incluido unas operaciones aritméticas que no hacen nada.

Veamos el resultado de pasar el profiler. Para ello, usamos las siguientes órdenes:

g++ -o p3a p3a.cc -g -pg

./p3a

gprof p3a

 

La salida es.

$ g++ -o p3a p3a.cc -g -pg

$ time ./p3a
-1173078384

real    0m0.060s
user    0m0.052s
sys    0m0.008s

$ gprof p3a
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total          
 time   seconds   seconds    calls  ms/call  ms/call  name   
 75.00      0.03     0.03  1000000     0.00     0.00  rellenar(int)
 25.00      0.04     0.01        1    10.00    10.00  sumar()
  0.00      0.04     0.00        1     0.00     0.00  global constructors keyed to a
  0.00      0.04     0.00        1     0.00     0.00  __static_initialization_and_destruction_0(int, int)


Vemos que el 75% del tiempo se gasta en la función rellenar (ya que se hacen muchas llamadas). El 25% del tiempo se gasta en la función suma.

 

¿Se puede mejorar?

Quitemos las operaciones aritméticas de la función rellenar (no hacen nada realmente) y además pongamos el bucle for dentro de la función (así sólo haremos una llamada a esta función). Esto debería darnos alguna mejora:

 

#include <iostream>

using namespace std;

#define MAX 1000000

 

int a[MAX];

int i = 0;

 

void rellenar(void) {

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

              if (i % 2 == 0){

                     a[i] = (i / 2);

              }else{

                     a[i] = (3 * i + 1);

              }

       }

}

 

int sumar(void) {

       int suma = 0;

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

              suma = suma + a[i];

       }

       return suma;

}

 

int main() {

 

       //una sola llamada a cada función

       rellenar();

       int total = sumar();

 

       cout << total << endl;

 

       return 0;

}

 

Como antes, para pasarle el profiler usamos las órdenes:

g++ -o p3b p3b.cc -g -pg

./p3b

gprof p3b

 

La salida del profiler en este caso es:

$ g++ -o p3b p3b.cc -g -pg

$ time ./p3b
-1173078384

real    0m0.021s
user    0m0.008s
sys    0m0.012s

$ gprof p3b
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total          
 time   seconds   seconds    calls  ms/call  ms/call  name   
100.00      0.01     0.01        1    10.00    10.00  sumar()
  0.00      0.01     0.00        1     0.00     0.00  global constructors keyed to a
  0.00      0.01     0.00        1     0.00     0.00  __static_initialization_and_destruction_0(int, int)
  0.00      0.01     0.00        1     0.00     0.00  rellenar()

Ahora casi todo el tiempo se gasta en la llamada a la función suma, y muy poco en la llamada a rellenar (sólo hacemos una llamada a cada una de las funciones, lo que hace que el tiempo empleado sea mucho menor).


A partir de este programa, se propone como práctica que tomeis un programa propio (del que tengáis el código fuente), le apliqueis el profiler y analiceis en qué funciones se gasta más tiempo. A partir del análisis, intentad mejorarlo (optimizar el cuello de botella detectado) y volved a pasarle el profiler para comprobar que lo habeis optimizado.

Se valorará que se haga con programas propios (en lugar de partir de esos ejemplos que hemos usado) y que se haga con otros compiladores distintos al gcc (p.ej. VC++, Perl, PHP, Java, etc).

2 comentarios:

  1. más información en:

    http://cplusplusworld.com/gnugprof.html

    http://www.chuidiang.com/clinux/herramientas/profiler.php

    http://sourceforge.net/projects/shinyprofiler/

    http://www.codersnotes.com/sleepy/sleepy

    ResponderEliminar
  2. Miriam García Pérez -> Irisgp6 de abril de 2012 a las 12:25

    En este enlace, no dice como analizar el rendimiento de nuestras aplicaciones con Eclipse Profiler Plugin para un lenguaje en java.

    Me ha gustado mucho, porque lo explica todo muy bien, e incluso te pone algunas pantallas.

    http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=eclipseProfiler

    ResponderEliminar