DISEÑO Y EVALUACIÓN DE CONFIGURACIONES

Práctica 3: DOMINGO LÓPEZ OLLER

Uso de un PROFILER en C++.

Lista de correo

Me he suscrito a la lista de correo con el correo [email protected].

Me he dado de alta en el sistema de envío de prácticas, y tengo el nick domingolopezoller y el id 51


INTRODUCCIÓN

El objetivo de esta práctica es comprender la utilidad del profiler a la hora de realizar nuestros programas lo más eficientes posibles y encontrar los puntos del programa más lentos para mejorarlos.
Hay que tener en cuenta que podemos estar mejorando una función mucho tiempo y sin embargo ser muy poco su uso por lo que funciones que en apariencia parecen eficientes pueden estar ocupando todo el tiempo y estas son las que tenemos que mejorar si podemos.
En la universidad nos hacen pensar en la eficiencia a nivel de número de búcles, tamaño de entradas y la recursividad, pero no nos dicen nada a cerca del contenido que tenemos que darle a una función y este punto es lo que quiero mostrar en la práctica.
No quiero ver un ejercicio super complicado que de primeras utilice celdas enlazadas y resulte mejor hacerlo con la STL de C++, eso ya nos lo enseñan en Teoría de Algoritmos y ya sabemos que lo segundo es más eficiente,sino que cosas que aparentemente tienen la misma eficiencia resulta que en la práctica los tiempos pueden variar mucho de hacerlo de una forma a otra.

El código que voy a utilizar en esta práctica es el siguiente:
CÓDIGO DE EJEMPLO parte 1 codigo parte 2 codigo

Este código puede verse aquí.
En este código por un lado voy a comprobar:
  1. La función división se encarga de realizar la división por 2 del dato pasado como argumento con el operador '/'
  2. La función desplaza se encarga de realizar el desplazamiento a la derecha de 2 o lo que es lo mismo dividir por 2 en binario
Salta a la vista que ambas funciones son las mismas, cambiando el nombre para identificarlas, y que se va a obtener el mismo resultado. Sin embargo de MP2 sabemos que la operación binaria va a ser más rápida y esto lo verificaremos con el profile.
  1. La función inserta modifica el valor en la posición pos del vector y realiza la suma desde la posición 0 a pos devolviendo el resultado
  2. La función inserta2 modifica el valor en la posición pos del vector e incrementa en la variable global suma el valor que se pasa por argumeno, no recorre todo el vector hasta pos, y devuelve el resultado
Del mismo modo que antes se tienen funciones que hacen lo mismo y por teoría de algoritmos se diría que ambas tienen una eficiencia constante O(1) dado que el bucle está acotado por pos y se hace en tiempo constante. Sin embargo esa 'constante' va a ser mucho mayor en inserta que en inserta2 y eso repercute en más tiempo y se verá en el profile.
Quizá solucionar eficiencia para las operaciones es costoso pero arreglar este tipo de funciones es una tontería y estamos ganando mucho tiempo.

Dado que realizar una ejecución no supone coste de tiempo para esta máquina hay que hacer que esas funciones se ejecuten una cantidad de veces considerable para ver así su duración en el archivo que nos dé el profile.
Para ejecutar el profile hay que seguir las siguientes instrucciones en Linux:
g++ dec3.cpp -o dec3 -pg -Wall
./dec3
gprof dec3 -q >resultado.txt
gprof dec3 -p >resultado2.txt
Este script se encuentra en el fichero ejecutar que hay en la carpeta archivos o pulsando Aquí
De esta ejecución se obtienen los ficheros resultado y resultado2 que muestran el resultado con diferente formato según modo texto o modo pantalla
Lo siguiente se puede extraer del fichero resultado.txt

index % time    self  children    called     name
                                                 
[1]    100.0    0.12    0.32                 main [1]
                0.20    0.00   10000/10000       inserta(int*, int, int) [2]
                0.07    0.00 10000000/10000000     division(int) [3]
                0.06    0.00 10000000/10000000     desplaza(int) [4]
                0.00    0.00   10000/10000       inserta2(int*, int, int) [10]
------------------------------------------------
                0.20    0.00   10000/10000       main [1]
[2]     45.5    0.20    0.00   10000         inserta(int*, int, int) [2]
------------------------------------------------
                0.07    0.00 10000000/10000000     main [1]
[3]     14.8    0.07    0.00 10000000         division(int) [3]
------------------------------------------------
                0.06    0.00 10000000/10000000     main [1]
[4]     12.5    0.06    0.00 10000000         desplaza(int) [4]
------------------------------------------------
                0.00    0.00   10000/10000       main [1]
[10]     0.0    0.00    0.00   10000         inserta2(int*, int, int) [10]
------------------------------------------------
                0.00    0.00       1/1           __do_global_ctors_aux [13]
[11]     0.0    0.00    0.00       1         global constructors keyed to suma [11]
                0.00    0.00       1/1           __static_initialization_and_destruction_0(int, int) [12]
------------------------------------------------
                0.00    0.00       1/1           global constructors keyed to suma [11]
[12]     0.0    0.00    0.00       1         __static_initialization_and_destruction_0(int, int) [12]
------------------------------------------------
Lo siguiente se puede extraer del fichero resultado2.txt
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  us/call  us/call  name    
 45.45      0.20     0.20    10000    20.00    20.00  inserta(int*, int, int)
 27.27      0.32     0.12                             main
 14.77      0.39     0.07 10000000     0.01     0.01  division(int)
 12.50      0.44     0.06 10000000     0.01     0.01  desplaza(int)
  0.00      0.44     0.00    10000     0.00     0.00  inserta2(int*, int, int)
  0.00      0.44     0.00        1     0.00     0.00  global constructors keyed to suma
  0.00      0.44     0.00        1     0.00     0.00  __static_initialization_and_destruction_0(int, int)
Puede verse que la información que se obtiene en uno y otro es la misma
Nota: Todos los ficheros se encuentran en la carpeta archivos adjunta a este fichero

Tal y como se predijo en los puntos a tratar en esta práctica vemos que se verifica el hecho de que división e inserta son las funciones que más tiempo consumen, sobre todo inserta por el bucle interno que tiene para 10000 valores, y que las funciones desplaza e inserta2 consumen mucho menos tiempo cuando están haciendo exactamente lo mismo.
El profile nos ha permitido comprobar que

CONCLUSIONES

Los profilers son herramientas muy útiles para poder desarrollar un código lo más eficiente posible ya que se puede ver que haciendo un cambio muy pequeños se ha conseguido pasar de una ocupación del 45.45% al 0% de tiempo total en el caso de la prueba de insertar e insertar2. Lo mismo pasa con división y desplaza donde se ve la mejora de 2.27% en desplaza.
Por lo tanto, el uso de estas funciones más optimizadas van a permitir obtener mejores resultados a la larga, ya que una función que se repita mucho y este muy optimizada hará que el programa en media sea rápido más que si optimizamos una de las funciones que se llama poco.

Ahora bien, particularmente creo que los profilers podrían aportar esa información sin tener que recurrir a desarrollar tantas evaluaciones, es decir, que con una sola te de esta información. Otro factor en contra de los profilers es su dificultad para entender el documento cuando el programa ya tiene cierta envergadura. Obviamente, he utilizado un ejemplo muy simple, pero a medida que se complica el documento interpretar este documento puede ser un auténtico infierno por la cantidad de funciones y la información que se presenta.
Nota: Yo estoy hablando del profiler de C++, es posible que otros profilers de JAVA o Visual Studio entre otros sí aporten o mejoren la información que proporciona este

DOMINGO LÓPEZ OLLER
Ingeniero Técnico Informática Sistemas
Práctica 3 de Diseño y Evaluación de configuraciones.
Hosted by www.Geocities.ws

1