Come posso cronometrare un segmento di codice per testare le prestazioni con Pythons timeit?


162

Ho uno script Python che funziona esattamente come dovrebbe, ma devo scrivere i tempi di esecuzione. Ho cercato su Google che avrei dovuto usaretimeit ma non riesco a farlo funzionare.

Il mio script Python è simile al seguente:

import sys
import getopt
import timeit
import random
import os
import re
import ibm_db
import time
from string import maketrans
myfile = open("results_update.txt", "a")

for r in range(100):
    rannumber = random.randint(0, 100)

    update = "update TABLE set val = %i where MyCount >= '2010' and MyCount < '2012' and number = '250'" % rannumber
    #print rannumber

    conn = ibm_db.pconnect("dsn=myDB","usrname","secretPWD")

for r in range(5):
    print "Run %s\n" % r        
    ibm_db.execute(query_stmt)
 query_stmt = ibm_db.prepare(conn, update)

myfile.close()
ibm_db.close(conn)

Ciò di cui ho bisogno è il tempo necessario per eseguire la query e scriverla nel file results_update.txt. Lo scopo è testare una dichiarazione di aggiornamento per il mio database con indici e meccanismi di ottimizzazione diversi.


La tua domanda era specifica timeit? Non credo. In tal caso, probabilmente dovresti rimuovere "with Pythons timeit" dal titolo.
Martin Thoma,

Risposte:


275

Puoi usare time.time()o time.clock()prima e dopo il blocco che vuoi cronometrare.

import time

t0 = time.time()
code_block
t1 = time.time()

total = t1-t0

Questo metodo non è esatto come timeit(non calcola la media di diverse esecuzioni) ma è semplice.

time.time()(in Windows e Linux) e time.clock()(in Linux) non sono abbastanza precisi per funzioni veloci (ottieni total = 0). In questo caso o se si desidera calcolare la media del tempo trascorso da più esecuzioni, è necessario chiamare manualmente la funzione più volte (come penso che già si faccia nel codice di esempio e timeit lo faccia automaticamente quando si imposta l' argomento numerico )

import time

def myfast():
   code

n = 10000
t0 = time.time()
for i in range(n): myfast()
t1 = time.time()

total_n = t1-t0

In Windows, come affermato da Corey nel commento, time.clock()ha una precisione molto più elevata (microsecondi anziché secondi) ed è preferito rispetto a time.time().


8
su Windows, usa time.clock () invece di time.time ()
Corey Goldberg,

4
Grazie Corey, perché? perché l'orologio è più preciso (microsecondi) o c'è qualcosa in più?
Joaquin,

11
Puoi usare timeit.default_timer () per rendere indipendente la tua piattaforma di codice; restituisce time.clock () o time.time () come appropriato per il sistema operativo.
Marc Stober,

6
Invece di selezionare un orologio a mano, usa timeit.default_timer; Python ha già fatto il lavoro per te. Ma in realtà, dovresti usare timeit.timeit(myfast, number=n)invece di reinventare la rotella di chiamata ripetitiva (e perdere il fatto che timeitdisabilita il Garbage Collector mentre esegue ripetutamente il codice).
Martijn Pieters

15
aggiornamento: time.clock () è ora deprecato. Ora dovresti usare time.time (). In realtà, dalla versione 3.3, l'opzione migliore sarebbe time.perf_counter ()
Madlozoz,

42

Se stai profilando il tuo codice e puoi usare IPython, ha la funzione magica %timeit.

%%timeit opera sulle cellule.

In [2]: %timeit cos(3.14)
10000000 loops, best of 3: 160 ns per loop

In [3]: %%timeit
   ...: cos(3.14)
   ...: x = 2 + 3
   ...: 
10000000 loops, best of 3: 196 ns per loop

36

A parte i tempi, questo codice che mostri è semplicemente errato: esegui 100 connessioni (ignorando completamente tutte tranne l'ultima), quindi quando esegui la prima chiamata di esecuzione le passi una variabile locale query_stmtche inizializzi solo dopo l'esecuzione chiamata.

Innanzitutto, rendi il tuo codice corretto, senza preoccuparti ancora dei tempi: cioè una funzione che crea o riceve una connessione ed esegue 100 o 500 o qualsiasi numero di aggiornamenti su quella connessione, quindi chiude la connessione. Una volta che il tuo codice funziona correttamente è il punto giusto in cui pensare di usarlo timeit!

In particolare, se la funzione che si desidera temporizzare è una chiamata senza parametri, foobarè possibile utilizzare timeit.timeit (2.6 o successivo - è più complicato in 2.5 e prima):

timeit.timeit('foobar()', number=1000)

È meglio specificare il numero di esecuzioni poiché l'impostazione predefinita, un milione, potrebbe essere elevata per il tuo caso d'uso (portando a dedicare molto tempo a questo codice ;-).


26
Dopo aver lottato con questo per gli ultimi minuti, voglio far sapere ai futuri spettatori che probabilmente vuoi anche passare una variabile di installazione se la tua funzione foobarè in un file principale. In questo modo: timeit.timeit('foobar()','from __main__ import foobar',number=1000)
Rich

3
In Python 2.7.8, potresti semplicemente usaretimeit.timeit( foobar, number=1000 )

9

Concentrati su una cosa specifica . L'I / O del disco è lento, quindi lo eliminerei dal test se tutto ciò che intendi modificare è la query del database.

E se devi programmare l'orario del tuo database, cerca invece gli strumenti del database, come chiedere il piano di query, e nota che le prestazioni variano non solo con la query esatta e quali indici hai, ma anche con il caricamento dei dati (quanti dati hai memorizzato).

Detto questo, puoi semplicemente inserire il tuo codice in una funzione ed eseguire quella funzione con timeit.timeit():

def function_to_repeat():
    # ...

duration = timeit.timeit(function_to_repeat, number=1000)

Ciò disabiliterebbe la garbage collection, chiamerebbe ripetutamente la function_to_repeat()funzione e calcolerebbe la durata totale di tali chiamatetimeit.default_timer() , che è l'orologio disponibile più preciso per la tua piattaforma specifica.

È necessario spostare il codice di installazione dalla funzione ripetuta; ad esempio, dovresti prima connetterti al database, quindi cronometrare solo le query. Utilizzare l' setupargomento per importare o creare tali dipendenze e passarle nella funzione:

def function_to_repeat(var1, var2):
    # ...

duration = timeit.timeit(
    'function_to_repeat(var1, var2)',
    'from __main__ import function_to_repeat, var1, var2', 
    number=1000)

afferrerebbe i globuli function_to_repeat, var1e var2dalla tua sceneggiatura e li passerebbe alla funzione ogni ripetizione.


Mettere il codice in una funzione è un passo che stavo cercando, dal momento che semplicemente rendere il codice una stringa e evaling non volerà per qualcosa di non completamente banale. thx
javadba,

2

Vedo che alla domanda è già stata data una risposta, ma voglio ancora aggiungere i miei 2 centesimi per lo stesso.

Ho anche affrontato uno scenario simile in cui devo testare i tempi di esecuzione per diversi approcci e quindi ho scritto un piccolo script, che chiama timeit su tutte le funzioni scritte in esso.

Lo script è disponibile anche come github gist qui .

Spero che possa aiutare te e gli altri.

from random import random
import types

def list_without_comprehension():
    l = []
    for i in xrange(1000):
        l.append(int(random()*100 % 100))
    return l

def list_with_comprehension():
    # 1K random numbers between 0 to 100
    l = [int(random()*100 % 100) for _ in xrange(1000)]
    return l


# operations on list_without_comprehension
def sort_list_without_comprehension():
    list_without_comprehension().sort()

def reverse_sort_list_without_comprehension():
    list_without_comprehension().sort(reverse=True)

def sorted_list_without_comprehension():
    sorted(list_without_comprehension())


# operations on list_with_comprehension
def sort_list_with_comprehension():
    list_with_comprehension().sort()

def reverse_sort_list_with_comprehension():
    list_with_comprehension().sort(reverse=True)

def sorted_list_with_comprehension():
    sorted(list_with_comprehension())


def main():
    objs = globals()
    funcs = []
    f = open("timeit_demo.sh", "w+")

    for objname in objs:
        if objname != 'main' and type(objs[objname]) == types.FunctionType:
            funcs.append(objname)
    funcs.sort()
    for func in funcs:
        f.write('''echo "Timing: %(funcname)s"
python -m timeit "import timeit_demo; timeit_demo.%(funcname)s();"\n\n
echo "------------------------------------------------------------"
''' % dict(
                funcname = func,
                )
            )

    f.close()

if __name__ == "__main__":
    main()

    from os import system

    #Works only for *nix platforms
    system("/bin/bash timeit_demo.sh")

    #un-comment below for windows
    #system("cmd timeit_demo.sh")

2

Ecco un semplice wrapper per la risposta di Steven. Questa funzione non esegue ripetute esecuzioni / media, ma ti evita di dover ripetere il codice di temporizzazione ovunque :)

'''function which prints the wall time it takes to execute the given command'''
def time_func(func, *args): #*args can take 0 or more 
  import time
  start_time = time.time()
  func(*args)
  end_time = time.time()
  print("it took this long to run: {}".format(end_time-start_time))

0

La suite di test non tenta di utilizzare l'importazione, timeitquindi è difficile dire quale fosse l'intento. Tuttavia, questa è una risposta canonica, quindi un esempio completo di timeitsembra in ordine, elaborando la risposta di Martijn .

I documenti pertimeit offrire molti esempi e bandiere da verificare. L'utilizzo di base sulla riga di comando è:

$ python -mtimeit "all(True for _ in range(1000))"
2000 loops, best of 5: 161 usec per loop
$ python -mtimeit "all([True for _ in range(1000)])"
2000 loops, best of 5: 116 usec per loop

Corri con -hper vedere tutte le opzioni. Python MOTW ha una grande sezione timeitche mostra come eseguire i moduli tramite importazione e stringhe di codice multilinea dalla riga di comando.

In forma di script, in genere lo uso in questo modo:

import argparse
import copy
import dis
import inspect
import random
import sys
import timeit

def test_slice(L):
    L[:]

def test_copy(L):
    L.copy()

def test_deepcopy(L):
    copy.deepcopy(L)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--n", type=int, default=10 ** 5)
    parser.add_argument("--trials", type=int, default=100)
    parser.add_argument("--dis", action="store_true")
    args = parser.parse_args()
    n = args.n
    trials = args.trials
    namespace = dict(L = random.sample(range(n), k=n))
    funcs_to_test = [x for x in locals().values() 
                     if callable(x) and x.__module__ == __name__]
    print(f"{'-' * 30}\nn = {n}, {trials} trials\n{'-' * 30}\n")

    for func in funcs_to_test:
        fname = func.__name__
        fargs = ", ".join(inspect.signature(func).parameters)
        stmt = f"{fname}({fargs})"
        setup = f"from __main__ import {fname}"
        time = timeit.timeit(stmt, setup, number=trials, globals=namespace)
        print(inspect.getsource(globals().get(fname)))

        if args.dis:
            dis.dis(globals().get(fname))

        print(f"time (s) => {time}\n{'-' * 30}\n")

Puoi facilmente inserire le funzioni e gli argomenti di cui hai bisogno. Usare cautela quando si usano funzioni impure e prendersi cura dello stato.

Uscita campione:

$ python benchmark.py --n 10000
------------------------------
n = 10000, 100 trials
------------------------------

def test_slice(L):
    L[:]

time (s) => 0.015502399999999972
------------------------------

def test_copy(L):
    L.copy()

time (s) => 0.01651419999999998
------------------------------

def test_deepcopy(L):
    copy.deepcopy(L)

time (s) => 2.136012
------------------------------

0

Un altro semplice esempio di timeit:

def your_function_to_test():
   # do some stuff...

time_to_run_100_times = timeit.timeit(lambda: your_function_to_test, number=100)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.