关于性能:在Python中度量所用的时间?

Measure time elapsed in Python?

我想要的是开始计算代码中某个地方的时间,然后获取经过的时间,以度量执行很少的函数所花费的时间。我想我用的时间模块是错误的,但是文档对我来说就是混乱。

1
2
3
4
5
6
import timeit

start = timeit.timeit()
print"hello"
end = timeit.timeit()
print end - start


如果只想测量两点之间经过的墙壁时钟时间,可以使用time.time()

1
2
3
4
5
6
import time

start = time.time()
print("hello")
end = time.time()
print(end - start)

这将以秒为单位给出执行时间。

3.3之后的另一个选择可能是使用perf_counterprocess_time,具体取决于您的要求。在3.3之前,建议使用time.clock(谢谢Amber)。但是,目前已弃用:

On Unix, return the current processor time as a floating point number
expressed in seconds. The precision, and in fact the very definition
of the meaning of"processor time", depends on that of the C function
of the same name.

On Windows, this function returns wall-clock seconds elapsed since the
first call to this function, as a floating point number, based on the
Win32 function QueryPerformanceCounter(). The resolution is typically
better than one microsecond.

Deprecated since version 3.3: The behaviour of this function depends
on the platform: use perf_counter() or process_time() instead,
depending on your requirements, to have a well defined behaviour.


timeit.default_timer代替timeit.timeit。前者自动提供平台上可用的最佳时钟和Python版本:

1
2
3
4
5
6
from timeit import default_timer as timer

start = timer()
# ...
end = timer()
print(end - start) # Time in seconds, e.g. 5.38091952400282

timeit.default_timer根据操作系统分配给time.time()或time.clock()。在python 3.3+上,默认的_计时器是所有平台上的time.perf_counter()。参见python-time.clock()与time.time()-精度?

参见:

  • 优化代码
  • 如何优化速度


Python 3只:

由于从python 3.3开始不推荐使用time.clock(),因此您将希望使用time.perf_counter()进行系统范围的计时,或使用time.process_time()进行进程范围的计时,就像使用time.clock()一样:

1
2
3
4
5
import time

t = time.process_time()
#do some stuff
elapsed_time = time.process_time() - t

新功能process_time不包括睡眠时间。


给定一个你想计时的函数,

测试:PY:

1
2
3
def foo():
    # print"hello"  
    return"hello"

使用timeit的最简单方法是从命令行调用它:

1
2
% python -mtimeit -s'import test' 'test.foo()'
1000000 loops, best of 3: 0.254 usec per loop

不要试图使用time.timetime.clock来比较功能的速度。它们会产生误导性的结果。

ps.不要将打印语句放入您希望计时的函数中;否则,所测量的时间将取决于终端的速度。


使用上下文管理器可以自动记住进入with块时的开始时间,然后在块退出时冻结结束时间,这很有趣。通过一点小技巧,您甚至可以从同一个上下文管理器函数中获得块内运行所用的时间计数。

核心库没有这个(但可能应该有)。一旦就位,您可以执行以下操作:

1
2
3
4
5
6
with elapsed_timer() as elapsed:
    # some lengthy code
    print("midpoint at %.2f seconds" % elapsed() )  # time so far
    # other lengthy code

print("all done at %.2f seconds" % elapsed() )

下面的ContextManager代码足以完成这一技巧:

1
2
3
4
5
6
7
8
9
10
from contextlib import contextmanager
from timeit import default_timer

@contextmanager
def elapsed_timer():
    start = default_timer()
    elapser = lambda: default_timer() - start
    yield lambda: elapser()
    end = default_timer()
    elapser = lambda: end-start

以及一些可运行的演示代码:

1
2
3
4
5
6
7
8
import time

with elapsed_timer() as elapsed:
    time.sleep(1)
    print(elapsed())
    time.sleep(2)
    print(elapsed())
    time.sleep(3)

注意,通过这个函数的设计,elapsed()的返回值在块退出时被冻结,并且进一步调用返回相同的持续时间(在这个toy示例中大约6秒)。


我更喜欢这个。timeit文件太令人困惑了。

1
2
3
4
5
6
7
8
9
from datetime import datetime

start_time = datetime.now()

# INSERT YOUR CODE

time_elapsed = datetime.now() - start_time

print('Time elapsed (hh:mm:ss.ms) {}'.format(time_elapsed))

注意,这里没有任何格式,我只是在打印输出中写入了hh:mm:ss,这样人们就可以解释time_elapsed


使用time.time来测量执行,可以给出命令的总体执行时间,包括计算机上其他进程所花费的运行时间。这是用户注意到的时间,但是如果您想比较不同的代码段/算法/函数,这是不好的。

关于timeit的更多信息:

  • 使用Timeit模块
  • timeit–计时执行少量python代码的时间

如果您想更深入地了解分析:

  • http://wiki.python.org/moin/pythonspeed/performancetips分析代码
  • 如何分析python脚本?

更新:去年我经常使用http://pythonhosted.org/line_profiler/并发现它非常有用,建议使用它而不是pythons profile模块。


下面是一个返回"hh:mm:ss"字符串的小计时器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Timer:
  def __init__(self):
    self.start = time.time()

  def restart(self):
    self.start = time.time()

  def get_time_hhmmss(self):
    end = time.time()
    m, s = divmod(end - self.start, 60)
    h, m = divmod(m, 60)
    time_str ="%02d:%02d:%02d" % (h, m, s)
    return time_str

用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Start timer
my_timer = Timer()

# ... do something

# Get time string:
time_hhmmss = my_timer.get_time_hhmmss()
print("Time elapsed: %s" % time_hhmmss )

# ... use the timer again
my_timer.restart()

# ... do something

# Get time:
time_hhmmss = my_timer.get_time_hhmmss()

# ... etc

python cprofile和pstats模块为测量某些函数所用的时间提供了极大的支持,而无需在现有函数周围添加任何代码。

例如,如果您有一个python脚本timefunctions.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time

def hello():
    print"Hello :)"
    time.sleep(0.1)

def thankyou():
    print"Thank you!"
    time.sleep(0.05)

for idx in range(10):
    hello()

for idx in range(100):
    thankyou()

要运行探查器并为文件生成统计信息,您只需运行:

1
python -m cProfile -o timeStats.profile timeFunctions.py

这样做的目的是使用cprofile模块对timefunctions.py中的所有函数进行概要分析,并在timestats.profile文件中收集统计信息。请注意,我们不必向现有模块(timefunctions.py)添加任何代码,这可以在任何模块中完成。

一旦您有了stats文件,就可以按如下方式运行pstats模块:

1
python -m pstats timeStats.profile

这将运行交互式统计浏览器,为您提供许多不错的功能。对于您的特定用例,您可以检查函数的统计信息。在我们的示例中,检查两个函数的统计信息显示了以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Welcome to the profile statistics browser.
timeStats.profile% stats hello
<timestamp>    timeStats.profile

         224 function calls in 6.014 seconds

   Random listing order was used
   List reduced from 6 to 1 due to restriction <'hello'>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       10    0.000    0.000    1.001    0.100 timeFunctions.py:3(hello)

timeStats.profile% stats thankyou
<timestamp>    timeStats.profile

         224 function calls in 6.014 seconds

   Random listing order was used
   List reduced from 6 to 1 due to restriction <'thankyou'>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      100    0.002    0.000    5.012    0.050 timeFunctions.py:7(thankyou)

这个虚拟的例子做的不多,但会给你一个可以做什么的概念。这种方法最好的一点是,我不必编辑任何现有的代码来获取这些数字,而且明显有助于分析。


这是另一个用于计时代码的上下文管理器-

用途:

1
2
3
4
5
6
from benchmark import benchmark

with benchmark("Test 1+1"):
    1+1
=>
Test 1+1 : 1.41e-06 seconds

或者,如果您需要时间值

1
2
3
4
5
6
with benchmark("Test 1+1") as b:
    1+1
print(b.time)
=>
Test 1+1 : 7.05e-07 seconds
7.05233786763e-07

基准:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from timeit import default_timer as timer

class benchmark(object):

    def __init__(self, msg, fmt="%0.3g"):
        self.msg = msg
        self.fmt = fmt

    def __enter__(self):
        self.start = timer()
        return self

    def __exit__(self, *args):
        t = timer() - self.start
        print(("%s :" + self.fmt +" seconds") % (self.msg, t))
        self.time = t

改编自http://dabeaz.blogspot.fr/2010/02/context-manager-for-timing-benchments.html


使用探查器模块。它给出了一个非常详细的概要。

1
2
import profile
profile.run('main()')

它输出如下内容:

1
2
3
4
5
6
7
8
9
10
11
          5 function calls in 0.047 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 :0(exec)
        1    0.047    0.047    0.047    0.047 :0(setprofile)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        0    0.000             0.000          profile:0(profiler)
        1    0.000    0.000    0.047    0.047 profile:0(main())
        1    0.000    0.000    0.000    0.000 two_sum.py:2(twoSum)

我发现它很有信息量。


(仅适用于IPython)您可以使用%timeit测量平均处理时间:

1
2
def foo():
    print"hello"

然后:

1
%timeit foo()

结果是:

1
10000 loops, best of 3: 27 μs per loop


关于Python 3:

1
2
3
4
from time import sleep, perf_counter as pc
t0 = pc()
sleep(1)
print(pc()-t0)

优雅而短小。


这是一种后来的反应,但也许对某个人来说是有目的的。这是一种非常干净的方法。

1
2
3
4
5
6
7
8
9
import time

def timed(fun, *args):
    s = time.time()
    r = fun(*args)
    print('{} execution took {} seconds.'.format(fun.__name__, time.time()-s))
    return(r)

timed(print,"Hello")

请记住,"print"是python 3中的函数,而不是python 2.7。但是,它与任何其他功能一起工作。干杯!


以下是我在阅读了许多好的答案以及其他几篇文章之后的发现。

首先,您总是希望使用timeit而不是time.time(并且在许多情况下是性能计数器API),因为

  • timeit选择操作系统和python版本上可用的最佳计时器。
  • timeit禁用垃圾收集,但这不是您可能想要或可能不想要的。
  • 现在的问题是,时间并不是那么简单,因为它需要设置,当你有大量的进口产品时,事情就会变得难看。理想情况下,您只需要一个装饰器或使用with块并测量时间。不幸的是,没有可用于此的内置模块,所以我在下面创建了一个小实用程序模块。

    定时实用模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    # utils.py
    from functools import wraps
    import gc
    import timeit

    def MeasureTime(f):
        @wraps(f)
        def _wrapper(*args, **kwargs):
            gcold = gc.isenabled()
            gc.disable()
            start_time = timeit.default_timer()
            try:
                result = f(*args, **kwargs)
            finally:
                elapsed = timeit.default_timer() - start_time
                if gcold:
                    gc.enable()
                print('Function"{}": {}s'.format(f.__name__, elapsed))
            return result
        return _wrapper

    class MeasureBlockTime:
        def __init__(self,name="(block)", no_print = False, disable_gc = True):
            self.name = name
            self.no_print = no_print
            self.disable_gc = disable_gc
        def __enter__(self):
            if self.disable_gc:
                self.gcold = gc.isenabled()
                gc.disable()
            self.start_time = timeit.default_timer()
        def __exit__(self,ty,val,tb):
            self.elapsed = timeit.default_timer() - self.start_time
            if self.disable_gc and self.gcold:
                gc.enable()
            if not self.no_print:
                print('Function"{}": {}s'.format(self.name, self.elapsed))
            return False #re-raise any exceptions

    如何计时功能

    现在,您只需在函数前面放置一个装饰器,就可以对其计时:

    1
    2
    3
    4
    5
    6
    7
    import utils

    @utils.MeasureTime
    def MyBigFunc():
        #do something time consuming
        for i in range(10000):
            print(i)

    如何计时代码块

    如果您想对代码的时间部分进行计时,那么只需将其放在with块中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import utils

    #somewhere in my code

    with utils.MeasureBlockTime("MyBlock"):
        #do something time consuming
        for i in range(10000):
            print(i)

    # rest of my code

    优势

    有几个半支持的版本浮动,所以我想指出一些亮点:

  • 出于前面描述的原因,请使用timeIt中的timer而不是time.time。
  • 定时期间禁用GC。
  • decorator接受带有命名或未命名参数的函数。
  • 能够禁用块定时打印(使用with utils.MeasureBlockTime() as t,然后使用t.elapsed)。
  • 能够保持GC为块计时启用。

  • 使用timeit的另一种方法:

    1
    2
    3
    4
    5
    6
    7
    from timeit import timeit

    def func():
        return 1 + 1

    time = timeit(func, number=1)
    print(time)

    我们也可以把时间转换成人类可读的时间。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import time, datetime

    start = time.clock()

    def num_multi1(max):
        result = 0
        for num in range(0, 1000):
            if (num % 3 == 0 or num % 5 == 0):
                result += num

        print"Sum is %d" % result

    num_multi1(1000)

    end = time.clock()
    value = end - start
    timestamp = datetime.datetime.fromtimestamp(value)
    print timestamp.strftime('%Y-%m-%d %H:%M:%S')

    我为这个做了一个库,如果你想测量一个函数,你可以这样做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from pythonbenchmark import compare, measure
    import time

    a,b,c,d,e = 10,10,10,10,10
    something = [a,b,c,d,e]

    @measure
    def myFunction(something):
        time.sleep(0.4)

    @measure
    def myOptimizedFunction(something):
        time.sleep(0.2)

    myFunction(input)
    myOptimizedFunction(input)

    https://github.com/karlheinzniebuhr/pythonbenchmark


    你可以用timeit。

    下面是一个关于如何使用python repl测试naive_func的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    >>> import timeit                                                                                        

    >>> def naive_func(x):                                                                                    
    ...     a = 0                                                                                            
    ...     for i in range(a):                                                                                
    ...         a += i                                                                                        
    ...     return a                                                                                          

    >>> def wrapper(func, *args, **kwargs):                                                                  
    ...     def wrapper():                                                                                    
    ...         return func(*args, **kwargs)                                                                  
    ...     return wrapper                                                                                    

    >>> wrapped = wrapper(naive_func, 1_000)                                                                  

    >>> timeit.timeit(wrapped, number=1_000_000)                                                              
    0.4458435332577161

    如果函数没有任何参数,则不需要包装函数。


    测量时间(秒):

    1
    2
    3
    4
    5
    6
    from timeit import default_timer as timer
    from datetime import timedelta

    start = timer()
    end = timer()
    print(timedelta(seconds=end-start))

    我能想到的唯一方法就是使用time.time()

    1
    2
    3
    4
    5
    6
    import time
    start = time.time()
    sleep(5) #just to give it some delay to show it working
    finish = time.time()
    elapsed = finish - start
    print(elapsed)

    希望能有所帮助。


    最好简单地使用timeit:(它为同一命令运行多个运行,并给出结果)。

    示例如下:

    1
    %timeit import pandas as pd

    除了IPython中的%timeit外,还可以将%%timeit用于多行代码段:

    1
    2
    3
    4
    5
    6
    7
    In [1]: %%timeit
       ...: complex_func()
       ...: 2 + 2 == 5
       ...:
       ...:

    1 s ± 1.93 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

    同样,它也可以用在Jupyter笔记本上,只需把魔术师%%timeit放在手机的开头。