¿Hay alguna forma sencilla de comparar el script de Python?

82

Por lo general, uso el comando de shell time. Mi propósito es probar si los datos son conjuntos pequeños, medianos, grandes o muy grandes, cuánto tiempo y uso de memoria será.

¿Alguna herramienta para Linux o simplemente Python para hacer esto?

noomz
fuente

Respuestas:

120

Eche un vistazo a timeit , el generador de perfiles de python y pycallgraph . También asegúrese de echar un vistazo al comentario a continuaciónnikicc mencionando " SnakeViz ". Le brinda otra visualización de los datos de creación de perfiles que puede ser útil.

cronométralo

def test():
    """Stupid test function"""
    lst = []
    for i in range(100):
        lst.append(i)

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

    # For Python>=3.5 one can also write:
    print(timeit.timeit("test()", globals=locals()))

Esencialmente, puede pasarle código Python como un parámetro de cadena, y se ejecutará en la cantidad de veces especificada e imprimirá el tiempo de ejecución. Los bits importantes de los documentos :

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None) Cree una Timerinstancia con la declaración dada, el código de configuración y la función de temporizador y ejecute su timeitmétodo con ejecuciones numéricas . El argumento global opcional especifica un espacio de nombres en el que ejecutar el código.

... y:

Timer.timeit(number=1000000)Número de ejecuciones de tiempo de la declaración principal. Esto ejecuta la instrucción de configuración una vez y luego devuelve el tiempo que lleva ejecutar la instrucción principal varias veces, medido en segundos como un valor flotante. El argumento es el número de veces que se recorre el ciclo, con un valor predeterminado de un millón. La declaración principal, la declaración de configuración y la función de temporizador que se utilizarán se pasan al constructor.

Nota: De forma predeterminada, timeitse apaga temporalmente garbage collectiondurante el tiempo. La ventaja de este enfoque es que hace que los tiempos independientes sean más comparables. Esta desventaja es que GC puede ser un componente importante del desempeño de la función que se mide. Si es así, GC se puede volver a habilitar como la primera declaración en la cadena de configuración . Por ejemplo:

timeit.Timer('for i in xrange(10): oct(i)', 'gc.enable()').timeit()

Perfilado

La creación de perfiles le dará una idea mucho más detallada sobre lo que está sucediendo. Aquí está el "ejemplo instantáneo" de los documentos oficiales :

import cProfile
import re
cProfile.run('re.compile("foo|bar")')

Que te dará:

      197 function calls (192 primitive calls) in 0.002 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1    0.000    0.000    0.001    0.001 re.py:212(compile)
     1    0.000    0.000    0.001    0.001 re.py:268(_compile)
     1    0.000    0.000    0.000    0.000 sre_compile.py:172(_compile_charset)
     1    0.000    0.000    0.000    0.000 sre_compile.py:201(_optimize_charset)
     4    0.000    0.000    0.000    0.000 sre_compile.py:25(_identityfunction)
   3/1    0.000    0.000    0.000    0.000 sre_compile.py:33(_compile)

Ambos módulos deberían darle una idea de dónde buscar los cuellos de botella.

Además, para familiarizarse con el resultado de profile, eche un vistazo a esta publicación

pycallgraph

NOTA pycallgraph ha sido oficialmente abandonado desde febrero de 2018 . Sin embargo, en diciembre de 2020 todavía estaba funcionando en Python 3.6. Sin embargo, siempre que no haya cambios fundamentales en la forma en que Python expone la API de creación de perfiles, debería seguir siendo una herramienta útil.

Este módulo utiliza graphviz para crear gráficos de llamadas como los siguientes:

ejemplo de gráfico de llamadas

Puede ver fácilmente qué caminos se utilizaron más tiempo por color. Puede crearlos usando la API de pycallgraph o usando un script empaquetado:

pycallgraph graphviz -- ./mypythonscript.py

Sin embargo, la sobrecarga es bastante considerable. Por lo tanto, para procesos que ya se ejecutan durante mucho tiempo, la creación del gráfico puede llevar algún tiempo.

exhumar
fuente
10
Si usa cProfile, también hay una opción para perfilar todo el script y guardar los resultados en un archivo con python -m cProfile -o results.prof myscript.py. El archivo oputput puede ser muy bien presentado en un navegador por medio de un programa llamado SnakeViz usandosnakeviz results.prof
nikicc
El último lanzamiento de pycallgraph fue en 2013 y ha sido abandonado oficialmente desde 2018
Boris
@Boris Es bueno saberlo. De hecho, lo usé ayer y, al menos por ahora, todavía funciona. Actualizaré la publicación. Gracias por la información.
exhuma
28

Utilizo un decorador simple para cronometrar la función.

def st_time(func):
    """
        st decorator to calculate the total time of a func
    """

    def st_func(*args, **keyArgs):
        t1 = time.time()
        r = func(*args, **keyArgs)
        t2 = time.time()
        print "Function=%s, Time=%s" % (func.__name__, t2 - t1)
        return r

    return st_func
Danyun Liu
fuente
es imprimir "Función =% s, Tiempo =% s"% (func .__ nombre__, t2 - t1) por supuesto. Gracias, realmente conveniente
user1941126
17

El timeitmódulo era lento y extraño, así que escribí esto:

def timereps(reps, func):
    from time import time
    start = time()
    for i in range(0, reps):
        func()
    end = time()
    return (end - start) / reps

Ejemplo:

import os
listdir_time = timereps(10000, lambda: os.listdir('/'))
print "python can do %d os.listdir('/') per second" % (1 / listdir_time)

Para mi, dice:

python can do 40925 os.listdir('/') per second

Este es un tipo primitivo de evaluación comparativa, pero es suficientemente bueno.

Sam Watkins
fuente
7
@exhuma, me olvido de los detalles y tal vez me apresuré en mi valoración. Creo que dije "extraño" porque se necesitan dos fragmentos de código como cadenas (en lugar de función / lambda). Pero puedo ver el valor en eso cuando se sincronizan segmentos de código de ejecución muy corta. ¡Supongo que dije "lento" porque el valor predeterminado es 1,000,000 de bucles y no vi cómo ajustar eso! Me gusta que mi código ya se divida por el número de repeticiones. Pero el tiempo es sin duda una mejor solución, me disculpo por no haberlo hecho.
Sam Watkins
11

Normalmente hago un rápido time ./script.pypara ver cuánto tiempo lleva. Sin embargo, eso no le muestra la memoria, al menos no por defecto. Puede utilizarlo /usr/bin/time -v ./script.pypara obtener mucha información, incluido el uso de memoria.

Martin Ueding
fuente
1
solo recuerda, este comando /usr/bin/timecon -vopción no está disponible por defecto en muchas distribuciones, tiene que ser instalado. sudo apt-get install timeen debian, ubuntu, etc. pacman -S timearchlinux
Rui Andrada
6

Memory Profiler para todas sus necesidades de memoria.

https://pypi.python.org/pypi/memory_profiler

Ejecute una instalación de pip:

pip install memory_profiler

Importar la biblioteca:

import memory_profiler

Agrega un decorador al artículo que deseas perfilar:

@profile
def my_func():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    return a

if __name__ == '__main__':
    my_func()

Ejecute el código:

python -m memory_profiler example.py

Reciba la salida:

 Line #    Mem usage  Increment   Line Contents
 ==============================================
 3                           @profile
 4      5.97 MB    0.00 MB   def my_func():
 5     13.61 MB    7.64 MB       a = [1] * (10 ** 6)
 6    166.20 MB  152.59 MB       b = [2] * (2 * 10 ** 7)
 7     13.61 MB -152.59 MB       del b
 8     13.61 MB    0.00 MB       return a

Los ejemplos son de los documentos, vinculados arriba.

John Von Neumann
fuente
3

Eche un vistazo a nose y a uno de sus complementos, este en particular.

Una vez instalado, nose es un script en su ruta, y al que puede llamar en un directorio que contiene algunos scripts de Python:

$: nosetests

Esto buscará en todos los archivos de Python en el directorio actual y ejecutará cualquier función que reconozca como prueba: por ejemplo, reconoce cualquier función con la palabra test_ en su nombre como prueba.

Entonces puede crear un script de Python llamado test_yourfunction.py y escribir algo como esto en él:

$: cat > test_yourfunction.py

def test_smallinput():
    yourfunction(smallinput)

def test_mediuminput():
    yourfunction(mediuminput)

def test_largeinput():
    yourfunction(largeinput)

Entonces tienes que correr

$: nosetest --with-profile --profile-stats-file yourstatsprofile.prof testyourfunction.py

y para leer el archivo de perfil, use esta línea de Python:

python -c "import hotshot.stats ; stats = hotshot.stats.load('yourstatsprofile.prof') ; stats.sort_stats('time', 'calls') ; stats.print_stats(200)"
dalloliogm
fuente
Me parece que esto hace lo mismo que el generador de perfiles de la biblioteca estándar de Python. Las pruebas no eran el tema de la pregunta. Además: se nosebasa en hotshot. Ya no se mantiene desde Python 2.5 y solo se mantiene "para uso especializado"
exhuma
2

Tenga cuidado, timeites muy lento, se necesitan 12 segundos en mi procesador medio para inicializar (o tal vez ejecutar la función). puedes probar esta respuesta aceptada

def test():
    lst = []
    for i in range(100):
        lst.append(i)

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test")) # 12 second

por simple cosa que usaré en su timelugar, en mi PC devuelve el resultado0.0

import time

def test():
    lst = []
    for i in range(100):
        lst.append(i)

t1 = time.time()

test()

result = time.time() - t1
print(result) # 0.000000xxxx
cieunteung
fuente
1
timeitejecuta su función muchas veces, para promediar el ruido. El número de repeticiones es una opción, consulte Tiempos de ejecución de evaluación comparativa en python o la última parte de la respuesta aceptada a esta pregunta.
Peter Cordes
1

La forma fácil de probar rápidamente cualquier función es usar esta sintaxis: %timeit my_code

Por ejemplo :

%timeit a = 1

13.4 ns ± 0.781 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
Arcyno
fuente
1

snakeviz visor interactivo para cProfile

https://github.com/jiffyclub/snakeviz/

cProfile se mencionó en https://stackoverflow.com/a/1593034/895245 y snakeviz se mencionó en un comentario , pero quería resaltarlo más.

Es muy difícil depurar el rendimiento del programa con solo mirar cprofile/pstats output, porque solo pueden totalizar los tiempos por función desde el primer momento.

Sin embargo, lo que realmente necesitamos en general es ver una vista anidada que contenga los seguimientos de la pila de cada llamada para encontrar los principales cuellos de botella fácilmente.

Y esto es exactamente lo que ofrece snakeviz a través de su vista "carámbano" predeterminada.

Primero tiene que volcar los datos de cProfile en un archivo binario, y luego puede serpentear en eso

pip install -u snakeviz
python -m cProfile -o results.prof myscript.py
snakeviz results.prof

Esto imprime una URL a stdout que puede abrir en su navegador, que contiene la salida deseada que se ve así:

ingrese la descripción de la imagen aquí

y luego puedes:

  • coloque el cursor sobre cada cuadro para ver la ruta completa al archivo que contiene la función
  • haga clic en un cuadro para que ese cuadro aparezca en la parte superior como una forma de acercar

Pregunta más orientada al perfil: ¿Cómo se puede crear un perfil de un script de Python?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
-1

Si no desea escribir código repetitivo para timeit y obtener resultados fáciles de analizar, eche un vistazo a benchmarkit . También guarda el historial de ejecuciones anteriores, por lo que es fácil comparar la misma función durante el transcurso del desarrollo.

# pip install benchmarkit

from benchmarkit import benchmark, benchmark_run

N = 10000
seq_list = list(range(N))
seq_set = set(range(N))

SAVE_PATH = '/tmp/benchmark_time.jsonl'

@benchmark(num_iters=100, save_params=True)
def search_in_list(num_items=N):
    return num_items - 1 in seq_list

@benchmark(num_iters=100, save_params=True)
def search_in_set(num_items=N):
    return num_items - 1 in seq_set

benchmark_results = benchmark_run(
   [search_in_list, search_in_set],
   SAVE_PATH,
   comment='initial benchmark search',
)  

Imprime en el terminal y devuelve la lista de diccionarios con los datos de la última ejecución. Los puntos de entrada de la línea de comandos también están disponibles.

ingrese la descripción de la imagen aquí

Si cambia N=1000000y vuelve a ejecutar

ingrese la descripción de la imagen aquí

v.grabovets
fuente