timeit 与计时装饰器

发布于 2024-08-08 17:15:44 字数 1633 浏览 3 评论 0 原文

我正在尝试计算一些代码的时间。首先,我使用了计时装饰器:

#!/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.)

yields

% 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 更快。

造成这种差异的原因是什么?

应该相信任何一种方法吗?

如果有,是哪一个?

I'm trying to time some code. First I used a timing decorator:

#!/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.)

yields

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

And here I use 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

Using timeit the results are virtually the same, but using the timing decorator it appears time_indexing is faster than time_izip.

What accounts for this difference?

Should either method be believed?

If so, which?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

思念满溢 2024-08-15 17:15:44

使用 functools 中的包装来改进 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

举个例子:

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

调用用@timing包裹的方法f

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

这样做的好处是保留了原函数的属性;也就是说,函数名称和文档字符串等元数据被正确保留在返回的函数上。

Use wrapping from functools to improve Matt Alcock's answer.

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

In an example:

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

Invoking method f wrapped with @timing:

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

The advantage of this is that it preserves attributes of the original function; that is, metadata like the function name and docstring is correctly preserved on the returned function.

失去的东西太少 2024-08-15 17:15:44

我会使用计时装饰器,因为您可以使用注释在代码周围散布计时,而不是让您的代码因计时逻辑而变得混乱。

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)

I would use a timing decorator, because you can use annotations to sprinkle the timing around your code rather than making you code messy with timing logic.

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

Using the decorator is easy either use annotations.

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

Or re-alias the function you want to time.

compute_magic = timeit(compute_magic)
也只是曾经 2024-08-15 17:15:44

使用时间。多次运行测试给我带来了更好的结果。

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.

Use timeit. Running the test more than once gives me much better results.

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.
错爱 2024-08-15 17:15:44

受到 Micah Smith 的回答的启发,我直接进行了有趣的打印(而不使用日志记录模块)。

下面方便在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)

Inspired by Micah Smith's answer, I made funcy print directly instead (and not use logging module).

Below is convenient for use at 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)
你是我的挚爱i 2024-08-15 17:15:44

我厌倦了 from __main__ import foo,现在使用这个 -- 对于简单的参数,%r 可以工作,
而不是在 Ipython 中。
(为什么timeit只适用于字符串,而不适用于thunks/闭包,即timefunc(f,任意args)?)


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 __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与ma​​in玩奇怪的把戏”,马尔泰利
运行-doctests-through-ipython

I got tired of from __main__ import foo, now use this -- for simple args, for which %r works,
and not in Ipython.
(Why does timeit works only on strings, not thunks / closures i.e. timefunc( f, arbitrary args ) ?)


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 __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 )

Added: see also "ipython plays weird tricks with main", Martelli
in running-doctests-through-ipython

纵性 2024-08-15 17:15:44

这就是您祈祷图书馆提供便携式解决方案的需求类型——DRY!幸运的是 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)
# ...

浏览有趣的文档以了解其他变体,例如不同的关键字参数和 @log_iter_durations

This is the type of need that you pray a library provides a portable solution -- DRY! Fortunately funcy.log_durations comes to the answer.

Example copied from documentation:

@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)
# ...

Browse the funcy documentation for other variants such as different keyword arguments and @log_iter_durations.

旧人九事 2024-08-15 17:15:44

这是一个适用于异步和同步函数的装饰器,并打印一个很好的人类可读输出,例如 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

这是一个 要点与此相同。

Here's a decorator that works for async and sync functions and prints a nice human readable output, e.g. 8us, 200ms, etc...

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

Here's a gist with the same.

自由范儿 2024-08-15 17:15:44

只是猜测,但差异可能是 range() 值差异的数量级吗?

来自您的原始来源:

alist=range(1000000)

来自您的 timeit 示例:

alist=range(100000)

对于它的价值,这里是我的系统上的结果,范围设置为 100 万:

$ 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

我无法让您的其他代码运行,因为我无法在我的系统上导入“装饰器”模块。


更新 - 当我在不涉及装饰器的情况下运行代码时,我看到了与您相同的差异。

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

感谢您提出这个问题;今天我学到了一些东西。 =)

Just a guess, but could the difference be the order of magnitude of difference in range() values?

From your original source:

alist=range(1000000)

From your timeit example:

alist=range(100000)

For what it's worth, here are the results on my system with the range set to 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

I wasn't able to get your other code to run, since I could not import the "decorator" module on my system.


Update - I see the same discrepancy you do when I run your code without the decorator involved.

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

Thanks for posting this question; I learned something today. =)

请止步禁区 2024-08-15 17:15:44

不管这个特定的练习如何,我认为使用 timeit 是更安全和可靠的选择。与您的解决方案不同,它也是跨平台的。

regardless of this particular exercise, I'd imagine that using timeit is much safer and reliable option. it is also cross-platform, unlike your solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文