web-dev-qa-db-fra.com

timeit versus timing decorator

J'essaie de chronométrer du code. J'ai d'abord utilisé un décorateur de chronométrage:

#!/usr/bin/env python

import time
from itertools import izip
from random import shuffle

def timing_val(func):
    def wrapper(*arg, **kw):
        '''source: http://www.daniweb.com/code/snippet368.html'''
        t1 = time.time()
        res = func(*arg, **kw)
        t2 = time.time()
        return (t2 - t1), res, func.__name__
    return wrapper

@timing_val
def time_izip(alist, n):
    i = iter(alist)
    return [x for x in izip(*[i] * n)]

@timing_val
def time_indexing(alist, n):
    return [alist[i:i + n] for i in range(0, len(alist), n)]

func_list = [locals()[key] for key in locals().keys()
             if callable(locals()[key]) and key.startswith('time')]
shuffle(func_list)  # Shuffle, just in case the order matters

alist = range(1000000)
times = []
for f in func_list:
    times.append(f(alist, 31))

times.sort(key=lambda x: x[0])
for (time, result, func_name) in times:
    print '%s took %0.3fms.' % (func_name, time * 1000.)

les rendements

% test.py
time_indexing took 73.230ms.
time_izip took 122.057ms.

Et ici j'utilise timeit:

%  python - m timeit - s '' 'alist=range(1000000);[alist[i:i+31] for i in range(0, len(alist), 31)]'
10 loops, best of 3:
    64 msec per loop
% python - m timeit - s 'from itertools import izip' 'alist=range(1000000);i=iter(alist);[x for x in izip(*[i]*31)]'
10 loops, best of 3:
    66.5 msec per loop

En utilisant timeit, les résultats sont pratiquement les mêmes, mais en utilisant le décorateur de synchronisation, il apparaît time_indexing est plus rapide que time_izip.

Qu'est-ce qui explique cette différence?

Faut-il croire à l'une ou l'autre méthode?

Si oui, lequel?

55
unutbu

Utilisez timeit. Faire plusieurs fois le test me donne de bien meilleurs résultats.

func_list=[locals()[key] for key in locals().keys() 
           if callable(locals()[key]) and key.startswith('time')]

alist=range(1000000)
times=[]
for f in func_list:
    n = 10
    times.append( min(  t for t,_,_ in (f(alist,31) for i in range(n)))) 

for (time,func_name) in Zip(times, func_list):
    print '%s took %0.3fms.' % (func_name, time*1000.)

->

<function wrapper at 0x01FCB5F0> took 39.000ms.
<function wrapper at 0x01FCB670> took 41.000ms.
18
Jochen Ritzel

Utilisez l'habillage de functools pour améliorer la réponse de Matt Alcock.

from functools import wraps
from time import time

def timing(f):
    @wraps(f)
    def wrap(*args, **kw):
        ts = time()
        result = f(*args, **kw)
        te = time()
        print 'func:%r args:[%r, %r] took: %2.4f sec' % \
          (f.__name__, args, kw, te-ts)
        return result
    return wrap

Dans un exemple:

@timing
def f(a):
    for _ in range(a):
        i = 0
    return -1

Appel de la méthode f encapsulée avec @timing:

func:'f' args:[(100000000,), {}] took: 14.2240 sec
f(100000000)

L'avantage de ceci est qu'il préserve les attributs de la fonction d'origine; c'est-à-dire que les métadonnées comme le nom de la fonction et la docstring sont correctement conservées sur la fonction renvoyée.

51
jonaprieto

J'utiliserais un décorateur de synchronisation, car vous pouvez utiliser des annotations pour saupoudrer la synchronisation autour de votre code plutôt que de vous compliquer le code avec la logique de synchronisation.

import time

def timeit(f):

    def timed(*args, **kw):

        ts = time.time()
        result = f(*args, **kw)
        te = time.time()

        print 'func:%r args:[%r, %r] took: %2.4f sec' % \
          (f.__name__, args, kw, te-ts)
        return result

    return timed

L'utilisation du décorateur est facile, soit utilisez des annotations.

@timeit
def compute_magic(n):
     #function definition
     #....

Ou renommez la fonction que vous souhaitez chronométrer.

compute_magic = timeit(compute_magic)
29
Matt Alcock

Je me suis lassé de from __main__ import foo, utilisez maintenant ceci - pour des arguments simples, pour lesquels% r fonctionne, et non en Ipython.
(Pourquoi timeit ne fonctionne que sur les chaînes, pas sur les thunks/closures, c'est-à-dire timefunc (f, arguments arbitraires)?)


import timeit

def timef( funcname, *args, **kwargs ):
    """ timeit a func with args, e.g.
            for window in ( 3, 31, 63, 127, 255 ):
                timef( "filter", window, 0 )
    This doesn't work in ipython;
    see Martelli, "ipython plays weird tricks with __main__" in Stackoverflow        
    """
    argstr = ", ".join([ "%r" % a for a in args]) if args  else ""
    kwargstr = ", ".join([ "%s=%r" % (k,v) for k,v in kwargs.items()]) \
        if kwargs  else ""
    comma = ", " if (argstr and kwargstr)  else ""
    fargs = "%s(%s%s%s)" % (funcname, argstr, comma, kwargstr)
        # print "test timef:", fargs
    t = timeit.Timer( fargs, "from __main__ import %s" % funcname )
    ntime = 3
    print "%.0f usec %s" % (t.timeit( ntime ) * 1e6 / ntime, fargs)

#...............................................................................
if __== "__main__":
    def f( *args, **kwargs ):
        pass

    try:
        from __main__ import f
    except:
        print "ipython plays weird tricks with __main__, timef won't work"
    timef( "f")
    timef( "f", 1 )
    timef( "f", """ a b """ )
    timef( "f", 1, 2 )
    timef( "f", x=3 )
    timef( "f", x=3 )
    timef( "f", 1, 2, x=3, y=4 )

Ajouté: voir aussi "ipython joue des tours bizarres avec main ", Martelli dans running-doctests-through-ipython

7
denis

Juste une supposition, mais la différence pourrait-elle être de l'ordre de grandeur de la différence dans les valeurs range ()?

De votre source d'origine:

alist=range(1000000)

À partir de votre exemple timeit:

alist=range(100000)

Pour ce que ça vaut, voici les résultats sur mon système avec la plage définie à 1 million:

$ python -V
Python 2.6.4rc2

$ python -m timeit -s 'from itertools import izip' 'alist=range(1000000);i=iter(alist);[x for x in izip(*[i]*31)]'
10 loops, best of 3: 69.6 msec per loop

$ python -m timeit -s '' 'alist=range(1000000);[alist[i:i+31] for i in range(0, len(alist), 31)]'
10 loops, best of 3: 67.6 msec per loop

Je n'ai pas pu faire fonctionner votre autre code, car je n'ai pas pu importer le module "décorateur" sur mon système.


pdate - Je vois la même différence que vous faites lorsque j'exécute votre code sans le décorateur impliqué.

$ ./test.py
time_indexing took 84.846ms.
time_izip took 132.574ms.

Merci d'avoir posté cette question; J'ai appris quelque chose aujourd'hui. =)

2
mpontillo

indépendamment de cet exercice particulier, j'imagine que l'utilisation de timeit est une option beaucoup plus sûre et fiable. il est également multiplateforme, contrairement à votre solution.

0
SilentGhost