Como medir el rendimiento. Clase 18

Anuncio
Rendimiento
Introducción a la Computación
Clase 18
Patricia Borensztejn
Un programa ejemplo
from random import randrange
def rellena (talla, rango):
valores = [0] * talla
for i in range(talla):
valores[i] = randrange(0, rango)
return valores
def burbuja (valores):
nuevo = valores[:]
for i in range(len(nuevo)):
for j in range(len(nuevo)-1-i):
if nuevo[j] > nuevo[j+1]:
nuevo[j], nuevo[j+1] = nuevo[j+1], nuevo[j]
print "Pasada %d " % i
print nuevo
return nuevo
Algoritmo de la Burbuja
# Programa principal
talla = 1000
rango = 100000
vector = rellena(talla, rango)
print "Vector desordenado: ", vector
ordenado = burbuja(vector)
print "Vector ordenado: ", ordenado
Vector desordenado: [1, 28, 54, 84, 89, 59,
11, 9, 50, 53]
Pasada 0
[1, 28, 54, 84, 59, 11, 9, 50, 53, 89]
Pasada 1
[1, 28, 54, 59, 11, 9, 50, 53, 84, 89]
Pasada 2
[1, 28, 54, 11, 9, 50, 53, 59, 84, 89]
Pasada 3
[1, 28, 11, 9, 50, 53, 54, 59, 84, 89]
Pasada 4
[1, 11, 9, 28, 50, 53, 54, 59, 84, 89]
Pasada 5
[1, 9, 11, 28, 50, 53, 54, 59, 84, 89]
Pasada 6
[1, 9, 11, 28, 50, 53, 54, 59, 84, 89]
Pasada 7
[1, 9, 11, 28, 50, 53, 54, 59, 84, 89]
Pasada 8
[1, 9, 11, 28, 50, 53, 54, 59, 84, 89]
Pasada 9
[1, 9, 11, 28, 50, 53, 54, 59, 84, 89]
Vector ordenado: [1, 9, 11, 28, 50, 53, 54,
59, 84, 89]
Medición de tiempos
• Utilizamos la función
clock() que está en el
módulo time.
• La función clock() (en
Windows y llamada por
python) devuelve el
número de segundos
transcurridos desde que
se inicia el proceso
hasta la invocación de
ella misma.
• Probemos:
>>> from time import clock
>>> print clock()
1.62086901271e-06
>>> print clock()
5.41601507732
>>> print clock()
7.46666723397
>>> print clock()
10.0847228233
>>> print clock()
11.1570711221
>>> print clock()
12.2909208643
>>> print clock()
14.0760029532
>>> print clock()
15.2212307907
>>> print clock()
16.2983776722
Medidas de Tiempo
•
•
•
•
•
Segundo
1 Milisegundo= 1ms=10-3 segundos=0,001 seg
1 Microsegundo= 1us=10-6 segundos=0,000 001 seg
1 Nanosegundo= 1ns=10-9 segundos=0,000 000 001 seg
1 Picosegundo= 1ps=10-12 segundos=0,000 000 000 001 seg
Midiendo con clock()
from random import randrange
from time import clock
……
# Programa principal
talla = 10
rango = 100
vector = rellena(talla, rango)
print "Vector desordenado: ", vector
tiempo_inicial = clock()
ordenado = burbuja(vector)
tiempo_final = clock()
print "Vector ordenado: ", ordenado
print "Tiempo de ejecucion de burbuja: %f " % (tiempo_final - tiempo_inicial)
• ¿Que observamos?
Midiendo con clock()
C:\Users\Patricia\programas_python>python burbuja.py
Vector desordenado: [50, 82, 54, 14, 41, 23, 94, 1, 45, 79]
Vector ordenado: [1, 14, 23, 41, 45, 50, 54, 79, 82, 94]
Tiempo de ejecucion de burbuja: 0.000027
C:\Users\Patricia\programas_python>python burbuja.py
Vector desordenado: [23, 99, 72, 20, 68, 50, 95, 55, 75, 10]
Vector ordenado: [10, 20, 23, 50, 55, 68, 72, 75, 95, 99]
Tiempo de ejecucion de burbuja: 0.000041
C:\Users\Patricia\programas_python>python burbuja.py
Vector desordenado: [39, 73, 55, 33, 21, 91, 8, 57, 85, 99]
Vector ordenado: [8, 21, 33, 39, 55, 57, 73, 85, 91, 99]
Tiempo de ejecucion de burbuja: 0.000025
C:\Users\Patricia\programas_python>python burbuja.py
Vector desordenado: [29, 30, 89, 17, 8, 15, 68, 9, 15, 8]
Vector ordenado: [8, 8, 9, 15, 15, 17, 29, 30, 68, 89]
Tiempo de ejecucion de burbuja: 0.000032
¿Qué observamos?
• Que el tiempo de ordenación es variable, ¿tendrá que
ver con los datos?
• Probemos: repetimos 10 veces con los mismos datos
(También cambié el tamaño del vector)
# Programa principal
talla = 1000
rango = 100000
vector = rellena(talla, rango)
for i in range(1,10):
tiempo_inicial = clock()
ordenado = burbuja(vector)
tiempo_final = clock()
print "Tiempo de ejecucion de burbuja: %f " % (tiempo_final - tiempo_inicial)
¿Qué observamos?
• Medimos:
Tiempo de ejecucion de burbuja: 0.155590
Tiempo de ejecucion de burbuja: 0.160534
Tiempo de ejecucion de burbuja: 0.173138
Tiempo de ejecucion de burbuja: 0.105864
Tiempo de ejecucion de burbuja: 0.102857
Tiempo de ejecucion de burbuja: 0.101671
Tiempo de ejecucion de burbuja: 0.102848
Tiempo de ejecucion de burbuja: 0.102689
Tiempo de ejecucion de burbuja: 0.102279
• Observamos que hay variación y que no depende de
los datos. Esto nos indica que para obtener un valor
del tiempo de ejecución debemos ejecutar varias
veces el programa y luego tomar la media.
¿Qué mas observamos?
• Medimos:
Tiempo de ejecucion de burbuja: 0.155590
Tiempo de ejecucion de burbuja: 0.160534
Tiempo de ejecucion de burbuja: 0.173138
Tiempo de ejecucion de burbuja: 0.105864
Tiempo de ejecucion de burbuja: 0.102857
Tiempo de ejecucion de burbuja: 0.101671
Tiempo de ejecucion de burbuja: 0.102848
Tiempo de ejecucion de burbuja: 0.102689
Tiempo de ejecucion de burbuja: 0.102279
• Observamos que la salida no tiene mas que seis
decimales: esa es la resolución que podemos
obtener en python.
Entonces, ¿cuantas veces repetimos la
medición?
• Podríamos repetir las mediciones durante un
tiempo mínimo, y luego hacer la media con los
valores obtenidos. El tiempo mínimo
dependería de nuestro programa
Media del Tiempo de ejecución
# Programa principal
talla = 10
rango = 100000
tiempo_minimo = 10
repeticiones= 0
vector = rellena(talla, rango)
tiempo_inicial = tiempo_final =clock()
while tiempo_final - tiempo_inicial < tiempo_minimo:
ordenado = burbuja(vector)
repeticiones+=1
tiempo_final = clock()
print "Tiempo de ejecucion de burbuja: %f " %\
((tiempo_final - tiempo_inicial)/repeticiones)
¿De qué depende?
• Del Procesador y del Sistema Operativo, es
decir, del soporte hardware que ofrece el
procesador y de cómo el SO usa esta
información.
• También de las librerías usadas por los
lenguajes de programación.
• Pero además, el tiempo de ejecución de un
programa en un procesador de propósito
general NO es constante.
Tiempo de ejecución variable
• El tiempo de ejecución de un conjunto de instrucciones es variable
en un procesador típico debido a que:
– Las instrucciones de nuestro programa NO se ejecutan en secuencia,
sino desordenamente. Esto es así porque los modernos procesadores
actuales siguen el modelo OOO (Out Of Order). Su tiempo de
ejecución, para cualquier programa, NO es constante. (Esta es una
carácterística muy indeseable para algunos sistemas de tiempo real,
donde nos gustaría que el tiempo de procesamiento sea constante)
– Los datos y las instrucciones residen en memoria principal, pero
cuando se ejecutan por primera vez son traídas cerca del procesador, a
la memoria caché. En un procesador moderno hay varias jerarquías de
memoria caché, y el llenado y vaciado de estas estructuras hacen
variable el tiempo de ejecución.
– Los programas cuando se ejecutan NO residen completamente en
Memoria Principal. Cuando hay que acceder al disco a buscar el resto
del programa, el SO detiene la ejecución del programa. Cuando
medimos con clock(), la rutina no devuelve el tiempo transcurrido real,
sino el tiempo que la CPU dedicó a ese proceso.
Midiendo en C
• También tenemos la función clock(), pero ésta devuelve el
tiempo medido en ciclos de procesador. Para obtener el
tiempo de ejecución en segundos debemos dividir por una
constante llamada CLOCKS_PER_SEC.
• Para averiguar el tiempo en unidades de tiempo (segundos,
por ejemplo) hay que dividir el número de ciclos devueltos
por clock() (que es un entero long) por el número de ciclos
por segundo de la máquina en la que ejecutamos.
• En realidad, la librería time que contiene a la función
clock() tiene una constante CLOCKS_PER_SEC. Podemos
averiguar cuanto vale en nuestro sistema. (Vale 1000)
• Y otra cosa: clock() de Python usa clock() de la librería de C,
solo que después hace una conversión a segundos, por lo
tanto el resultado es un float.
Tiempo de Ejecución
• La duración del ciclo de reloj viene dada por la
frecuencia del procesador
ciclos de reloj
T_ejec 
frecuencia del reloj
• Frecuencia del reloj = ciclos por segundo (1 Hz. =
1 ciclo/sec).
• Frecuencia es la inversa del período del reloj.
• Un reloj de 200 Mhz, tiene una duración de ciclo
de ..............?
1
200  10 6
 10 9  5 nanoseconds
Tiempo de Ejecución
• Tiempo de Ejecución medido en términos de
ciclos de reloj
T_ejec  ciclos de reloj * duración del ciclo de reloj
segundos
ciclos
segundos


programa programa
ciclo
• Duración del ciclo = tiempo entre ticks de reloj
(segundos por ciclo) es el período del reloj
time
• Clock() cuenta Ciclos o Tics de reloj
Burbuja en C
Medimos en C
Tiempo medio de ejecucion de burbuja: 0.004471
Process returned 48 (0x30) execution time : 10.031 s
Press any key to continue.
• Ordenamiento de un vector de 1000 enteros
con valores entre 0 y 100 000.
• Tiempo de repeticiones: 10 segundos
Medimos en Python
Tiempo de ejecucion de burbuja: 0.101502
>>>
• Ordenamiento de un vector de 1000 enteros
con valores entre 0 y 100 000.
• Tiempo de repeticiones: 10 segundos
C vs. Python
• Ya lo sabíamos: un programa en un lenguaje
interpretado es menos eficiente que el mismo
programa en un lenguaje compilado.
• Para la misma cantidad de datos a ordenar, C
es 23 veces más rápido que Python!
Tejec en Python = 0.101502
Tejec en C = 0.004471
22,7
Clock()
• Es una función de la librería estándar de C, no
dependiente del SO, por lo tanto se puede
utilizar en cualquier SO.
• Su precisión está en el orden de los
milisegundos. Para saberlo, ejecutar este
código:
Precisión de clock()
int main()
{
clock_t t1, t2;
t1 = t2 = clock();
// loop until t2 gets a different value
while(t1 == t2){
t2 = clock();
}
// print resolution of clock()
printf ("precision = %.8f ms\n", (double) (t2 - t1) / CLOCKS_PER_SEC * 1000);
• En mi máquina, intel core i5 2,5Gigahertz, 4Giga
memoria, windows 7: 1ms
Como funciona Clock()
• Hay un reloj del sistema que interrumpe al
procesador cada x milisegundos, en cada
interrupción incrementa un contador de tiempo.
(en mi máquina, x=1mseg). Ese valor es el que
consulta la función clock(). El valor está dado en
ticks del reloj del sistema.
• Pero también hay un reloj del hardware, que
cuenta ciclos verdaderos, es decir, que tiene la
precisión de la frecuencia del procesador, en el
orden de los nanosegundos. ¿Como accedemos a
esos contadores hardware? ….. En lenguaje
ensamblador…
Reloj del procesador
• Todos los procesadores son sistemas
síncronos, es decir, tienen un reloj que
sincroniza las operaciones:
– Fetch, decode, execute
• El reloj de mi procesador tiene un
Tciclo=1/frecuencia= 1/2.5 Gigahertz
=1/2.5*109 ciclos por seg
=(1*109 nanoseg por segundo )/2.5*109 ciclos por seg
= 0.4 nanoseg.
Performance Counters
• Dependiendo de la implementación, los procesadores
tienen un conjunto de registros que se utilizan para medir
rendimiento a muy bajo nivel, es decir, a nivel del reloj del
procesador.
• Estos registros pueden ser programados (por el sistema, es
decir, son registros privilegiados) para contar una variada
cantidad de eventos del programa:
– Número de ciclos de procesador: lleva la cuenta de los ciclos
consumidos por la cpu desde su arranque
– Número de accesos a memoria, número de hits o misses en la
memoria caché
– Tiempo de un acceso a memoria.
– Y muchisimos mas eventos
Performance Counters
• Windows inicialmente tenía unas funciones que podían
consultar el valor de estos registros:
– QueryPerformanceCounters(): devuelve el número de ticks
– QueryPerformanceFrequency(): devuelve la frecuencia en
ciclos por segundo
• Ambas funciones ahora no consultan esos registros,
aunque quizás son un poco mas precisas que clock()
• Si necesitamos mucha precisión en nuestras medidas,
tenemos que usar otra herramienta: gprof.
Gprof
• Es un profiler. Es decir, una herramienta (un programa)
que realiza un análisis dinámico de un programa. Se
debe preparar el programa para su ejecución mediante
un profiler:
Compilar: cc -g -c myprog.c utils.c -pg
Enlazar: cc -o myprog myprog.o utils.o -pg
Ejecutar: myprog
• La salida de myprog es un archivo llamado gmon.out
• Luego ejecutamos el profiler
– gprof myprog.exe >salida
• En el archivo salida, tendremos los datos de la
ejecución del programa.
Gprof
Each sample counts as 0.01 seconds.
% cumulative self
self total
time seconds seconds calls ms/call ms/call name
33.34 0.02 0.02 7208 0.00 0.00 open
16.67 0.03 0.01 244 0.04 0.12 offtime
16.67 0.04 0.01
8 1.25 1.25 memccpy
16.67 0.05 0.01
7 1.43 1.43 write
16.67 0.06 0.01
mcount
0.00 0.06 0.00 236 0.00 0.00 tzset
0.00 0.06 0.00 192 0.00 0.00 tolower
0.00 0.06 0.00
47 0.00 0.00 strlen
0.00 0.06 0.00
45 0.00 0.00 strchr
0.00 0.06 0.00
1 0.00 50.00 main
0.00 0.06 0.00
1 0.00 0.00 memcpy
0.00 0.06 0.00
1 0.00 10.11 print
0.00 0.06 0.00
1 0.00 0.00 profil
0.00 0.06 0.00
1 0.00 50.00 report
Ejercicio de rendimiento (1)
• Realizar varias mediciones para distintos tamaños
de vectores, distintos algoritmos, y distintos
lenguajes (C y Python)
• Medir con clock()
• Graficar los resultados, algo así:
Ejercicio de rendimiento (2)
• Utilizar un programa que esté construído con
unas cuantas funciones.
• Ejecutarlo con el profiler gprof (hay que ver si
esta instalado)
• Estudiar los resultados de la salida
• Manual del gnu:
– http://www.cs.utah.edu/dept/old/texinfo/as/gpro
f.html#SEC1
Descargar