Время против декоратора времени
Я пытаюсь определить время кода. Сначала я использовал декоратор времени:
#!/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.)
доходность
% test.py
time_indexing took 73.230ms.
time_izip took 122.057ms.
И здесь я использую 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
При использовании timeit результаты практически одинаковы, но при использовании временного декоратора time_indexing
быстрее чем time_izip
,
Чем объясняется эта разница?
Следует ли верить какому-либо методу?
Если так, то какой?
9 ответов
Используйте время. Запуск теста более одного раза дает мне гораздо лучшие результаты.
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.
Использовать упаковку из functools
улучшить ответ Мэтта Алкока.
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
В примере:
@timing
def f(a):
for _ in range(a):
i = 0
return -1
Вызывая метод f
завернутый в @timing
:
func:'f' args:[(100000000,), {}] took: 14.2240 sec
f(100000000)
Преимущество этого в том, что он сохраняет атрибуты исходной функции; то есть метаданные, такие как имя функции и строка документации, правильно сохраняются в возвращаемой функции.
Я бы использовал декоратор синхронизации, потому что вы можете использовать аннотации для разброса времени вокруг вашего кода, вместо того, чтобы запутывать код в логике синхронизации.
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
Использование декоратора легко или использовать аннотации.
@timeit
def compute_magic(n):
#function definition
#....
Или переименуйте функцию, которую вы хотите, чтобы время.
compute_magic = timeit(compute_magic)
Вдохновленный ответом Мики Смита, я вместо этого сделал прямую печать (и не использовал модуль ведения журнала).
Ниже удобно для использования в google colab.
# pip install funcy
from funcy import print_durations
@print_durations()
def myfunc(n=0):
for i in range(n):
pass
myfunc(123)
myfunc(123456789)
# 5.48 mks in myfunc(123)
# 3.37 s in myfunc(123456789)
Я устал от from __main__ import foo
, теперь используйте это - для простых аргументов, для которых работает%r, а не в Ipython.
(Почему timeit
работает только со строками, а не с thunks / closures, т.е. timefunc( f, произвольные аргументы)?)
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 Stackru
"""
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 __name__ == "__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 )
Добавлено: см. Также "ipython разыгрывает странные трюки с главными", Мартелли в запуске-doctests-through-ipython
Это тот тип потребности, о котором вы молитесь, чтобы библиотека предоставила портативное решение - СУХОЕ! К счастью, ответ приходит на помощь funcy.log_durations.
Пример скопирован из документации:
@log_durations(logging.info)
def do_hard_work(n):
samples = range(n)
# ...
# 121 ms in do_hard_work(10)
# 143 ms in do_hard_work(11)
# ...
Просмотрите документацию funcy для других вариантов, таких как различные аргументы ключевых слов и @log_iter_durations
.
Просто предположение, но может ли разница быть на порядок разности значений range()?
Из вашего первоисточника:
alist=range(1000000)
От твоего timeit
пример:
alist=range(100000)
Для чего это стоит, вот результаты на моей системе с диапазоном, установленным в 1 миллион:
$ 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
Я не смог запустить ваш другой код, так как не смог импортировать модуль "decorator" в моей системе.
Обновление - я вижу то же несоответствие, что и вы, когда запускаете ваш код без участия декоратора.
$ ./test.py
time_indexing took 84.846ms.
time_izip took 132.574ms.
Спасибо за размещение этого вопроса; Я кое-что узнал сегодня. знак равно
Независимо от этого конкретного упражнения, я думаю, что с помощью timeit
это гораздо более безопасный и надежный вариант. это также кроссплатформенное, в отличие от вашего решения.
Вот декоратор, который работает с асинхронными и синхронизирующими функциями и выводит удобный для чтения вывод, например, 8us, 200ms и т.д.
import asyncio
import time
from typing import Callable, Any
def timed(fn: Callable[..., Any]) -> Callable[..., Any]:
"""
Decorator log test start and end time of a function
:param fn: Function to decorate
:return: Decorated function
Example:
>>> @timed
>>> def test_fn():
>>> time.sleep(1)
>>> test_fn()
"""
def wrapped_fn(*args: Any, **kwargs: Any) -> Any:
start = time.time()
print(f'Running {fn.__name__}...')
ret = fn(*args, **kwargs)
duration_str = get_duration_str(start)
print(f'Finished {fn.__name__} in {duration_str}')
return ret
async def wrapped_fn_async(*args: Any, **kwargs: Any) -> Any:
start = time.time()
print(f'Running {fn.__name__}...')
ret = await fn(*args, **kwargs)
duration_str = get_duration_str(start)
print(f'Finished {fn.__name__} in {duration_str}')
return ret
if asyncio.iscoroutinefunction(fn):
return wrapped_fn_async
else:
return wrapped_fn
def get_duration_str(start: float) -> str:
"""Get human readable duration string from start time"""
duration = time.time() - start
if duration > 1:
duration_str = f'{duration:,.3f}s'
elif duration > 1e-3:
duration_str = f'{round(duration * 1e3)}ms'
elif duration > 1e-6:
duration_str = f'{round(duration * 1e6)}us'
else:
duration_str = f'{duration * 1e9}ns'
return duration_str
Вот суть того же самого.