如何使用timeit模块


351

我了解做什么的概念,timeit但不确定如何在代码中实现。

我怎样才能比较两个功能,比方说insertion_sorttim_sort,用timeit

Answers:


266

timeit的工作方式是运行一次安装代码,然后重复调用一系列语句。因此,如果要测试排序,则需要格外小心,以免就地进行一次排序不会影响已排序数据的下一遍(这当然会使Timsort发光,因为它表现最佳当数据已经部分排序时)。

这是有关如何设置排序测试的示例:

>>> import timeit

>>> setup = '''
import random

random.seed('slartibartfast')
s = [random.random() for i in range(1000)]
timsort = list.sort
'''

>>> print min(timeit.Timer('a=s[:]; timsort(a)', setup=setup).repeat(7, 1000))
0.334147930145

请注意,这一系列语句在每次通过时都会对未排序的数据进行全新复制。

另外,请注意运行测量套件七次并仅保留最佳时间的计时技术-这确实可以帮助减少由于系统上正在运行其他进程而导致的测量失真。

这些是我正确使用timeit的技巧。希望这可以帮助 :-)


8
是的,它包括列表副本(与排序本身相比非常快)。但是,如果您不进行复制,则第一遍将对列表进行排序,而其余的则无需做任何工作。如果您只想知道排序的时间,请在有和没有的情况下运行上面的命令,并加以timsort(a)区别:-)
Raymond Hettinger 2012年

我建议每次设置重复7次,然后取平均值。而不是相反。这样,如果由于其他过程而导致的每个尖峰很有可能被完全忽略而不是被平均掉。
2012年

75
@max使用min()而不是计时的平均值。这是我,蒂姆·彼得斯和吉多·范·罗苏姆的建议。最快的时间表示在加载缓存并且系统不忙于其他任务时算法可以执行的最佳性能。所有的时间都是嘈杂的-最快的时间是最少的嘈杂。很容易看出,最快的时序是最可重复的,因此在对两个不同的实现进行时序设置时最有用。
Raymond Hettinger 2012年

4
您可以计算1000个输入的平均值(很好,总计,但它是等效的)。然后重复7次,并取最小值。您需要平均1000多个输入,因为您需要平均(而非最佳情况)算法复杂度。正是由于您给出的原因,您才需要最低要求。我以为我可以通过选择一个输入,运行算法7次,花最少的时间来改善您的方法。然后针对1000个不同的输入重复该操作,并取平均值。我没有意识到的是,您.repeat(7,1000)已经做到了(使用相同的种子)!因此,您的解决方案是完美的IMO。
最大

5
我只能补充一下,如何分配7000次执行的预算(例如.repeat(7, 1000)vs. .repeat(2, 3500)vs .repeat(35, 200)应该取决于系统负载导致的错误与输入可变性导致的错误相比。在极端情况下,如果您的系统始终处于高负载状态,并且在执行时间分配的左侧看到一条长长的细尾巴(当您以罕见的空闲状态捕获它时),您甚至会发现.repeat(7000,1)它比.repeat(7,1000)您更有用。预算不能超过7000次。
最大

277

如果要timeit在交互式Python会话中使用,有两个方便的选项:

  1. 使用IPython Shell。它具有方便的%timeit特殊功能:

    In [1]: def f(x):
       ...:     return x*x
       ...: 
    
    In [2]: %timeit for x in range(100): f(x)
    100000 loops, best of 3: 20.3 us per loop
  2. 在标准的Python解释器中,您可以通过__main__在setup语句中导入它们来访问在交互式会话期间先前定义的函数和其他名称:

    >>> def f(x):
    ...     return x * x 
    ... 
    >>> import timeit
    >>> timeit.repeat("for x in range(100): f(x)", "from __main__ import f",
                      number=100000)
    [2.0640320777893066, 2.0876040458679199, 2.0520210266113281]

97
+1用于显示该from __main__ import f技术。我认为这没有应有的知名度。在这种情况下,对函数或方法调用进行计时的情况很有用。在其他情况下(定时执行一系列步骤),它的作用较小,因为它会引入函数调用开销。
Raymond Hettinger

15
您可以做%timeit f(x)
2014年

注意:“导入f”设置可访问fa快速本地读取-不能完全反映典型普通代码中的全局函数调用(快速函数简称)。在Py3.5 +中,可以提供真实的全局变量:“在版本3.5中更改:添加了可选的全局变量参数”。在timeit模块的全局变量之前,这是不可避免的(这没有多大意义)。sys._getframe(N).f_globals从一开始,可能是调用代码()的全局变量。
kxr

140

我会告诉您一个秘密:最好的使用方法timeit是在命令行上。

在命令行上,进行timeit适当的统计分析:它告诉您最短运行花费了多长时间。这很好,因为所有计时错误都是正的。因此,最短的时间误差最小。没有办法得到负错误,因为计算机的计算速度永远不可能超过其计算速度!

因此,命令行界面:

%~> python -m timeit "1 + 2"
10000000 loops, best of 3: 0.0468 usec per loop

这很简单,是吗?

您可以设置以下内容:

%~> python -m timeit -s "x = range(10000)" "sum(x)"
1000 loops, best of 3: 543 usec per loop

也是有用的!

如果需要多行,则可以使用外壳程序的自动延续或使用单独的参数:

%~> python -m timeit -s "x = range(10000)" -s "y = range(100)" "sum(x)" "min(y)"
1000 loops, best of 3: 554 usec per loop

这给出了一个设置

x = range(1000)
y = range(100)

和时代

sum(x)
min(y)

如果您想要更长的脚本,则可能会倾向于timeit使用Python脚本。我建议避免这种情况,因为在命令行上分析和计时会更好。相反,我倾向于制作shell脚本:

 SETUP="

 ... # lots of stuff

 "

 echo Minmod arr1
 python -m timeit -s "$SETUP" "Minmod(arr1)"

 echo pure_minmod arr1
 python -m timeit -s "$SETUP" "pure_minmod(arr1)"

 echo better_minmod arr1
 python -m timeit -s "$SETUP" "better_minmod(arr1)"

 ... etc

由于要进行多次初始化,因此可能需要更长的时间,但是通常这没什么大不了的。


但是,如果timeit在模块内部使用该怎么办?

好吧,简单的方法是:

def function(...):
    ...

timeit.Timer(function).timeit(number=NUMBER)

这样您就可以累积(而不是最短!)时间来运行该次数。

为了获得良好的分析效果,请使用.repeat并采取以下最低限度的措施:

min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))

通常应将此与结合使用,functools.partial而不是lambda: ...降低开销。因此,您可能会遇到以下情况:

from functools import partial

def to_time(items):
    ...

test_items = [1, 2, 3] * 100
times = timeit.Timer(partial(to_time, test_items)).repeat(3, 1000)

# Divide by the number of repeats
time_taken = min(times) / 1000

您也可以:

timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)

这将使您从命令行更接近界面,但是方式要少得多。将"from __main__ import ..."让您使用代码从您的主模块所创造的人工环境内timeit

值得注意的是,这是一个方便包装Timer(...).timeit(...),因此在时间安排上并不是特别好。我个人更喜欢使用Timer(...).repeat(...)上面显示的内容。


警告事项

timeit到处都有一些警告。

  • 开销不占。说您要计时x += 1,找出加法需要多长时间:

    >>> python -m timeit -s "x = 0" "x += 1"
    10000000 loops, best of 3: 0.0476 usec per loop

    好吧,这不是 0.0476 µs。您只知道它比这还。所有错误均为正。

    因此,尝试找到开销:

    >>> python -m timeit -s "x = 0" ""      
    100000000 loops, best of 3: 0.014 usec per loop

    仅从定时开始,这就是30%的开销!这会大大歪曲相对的时间安排。但是您只真正关心添加的时间。查找时间x也需要包含在开销中:

    >>> python -m timeit -s "x = 0" "x"
    100000000 loops, best of 3: 0.0166 usec per loop

    差别不大,但是就在那里。

  • 变异方法很危险。

    >>> python -m timeit -s "x = [0]*100000" "while x: x.pop()"
    10000000 loops, best of 3: 0.0436 usec per loop

    但这是完全错误的! x是第一次迭代后的空列表。您需要重新初始化:

    >>> python -m timeit "x = [0]*100000" "while x: x.pop()"
    100 loops, best of 3: 9.79 msec per loop

    但是那样您就会有很多开销。分别说明。

    >>> python -m timeit "x = [0]*100000"                   
    1000 loops, best of 3: 261 usec per loop

    请注意,在这里减去开销是合理的,仅是因为开销只是时间的一小部分。

    对于你的榜样,值得一提的是,这两个插入排序和蒂姆排序有完全不同寻常的已排序的列表时序行为。这意味着,random.shuffle如果您想避免破坏时间安排,就需要进行两次排序。


1
usec是什么意思?是微秒?
哈桑·伊克巴尔

2
@HasanIqbalAnik是的。
Veedrac

@StefanPochmann因为它不会尝试多次采样。
Veedrac '17

这个答案的读者可能也对从程序使用Pythontimeit感兴趣,但其功能与命令行相同?
格雷厄姆

@Veedrac考虑到您要减去纯计时开销timeitpass语句,因此在没有给出任何参数的情况下执行一条语句,这当然会花费一些时间。如果给出任何参数,pass不会执行,因此0.014从每个时间点减去一些有用的参数都是不正确的。
阿内(Arne)

99

如果要快速比较两个代码/功能块,可以执行以下操作:

import timeit

start_time = timeit.default_timer()
func1()
print(timeit.default_timer() - start_time)

start_time = timeit.default_timer()
func2()
print(timeit.default_timer() - start_time)

43

我发现使用timeit的最简单方法是从命令行:

给定test.py

def InsertionSort(): ...
def TimSort(): ...

运行timeit是这样的:

% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'

18

对我来说,这是最快的方法:

import timeit
def foo():
    print("here is my code to time...")


timeit.timeit(stmt=foo, number=1234567)

12
# Генерация целых чисел

def gen_prime(x):
    multiples = []
    results = []
    for i in range(2, x+1):
        if i not in multiples:
            results.append(i)
            for j in range(i*i, x+1, i):
                multiples.append(j)

    return results


import timeit

# Засекаем время

start_time = timeit.default_timer()
gen_prime(3000)
print(timeit.default_timer() - start_time)

# start_time = timeit.default_timer()
# gen_prime(1001)
# print(timeit.default_timer() - start_time)


3

让我们在以下每个目录中设置相同的字典并测试执行时间。

setup参数基本上是在设置字典

编号是要运行的代码1000000次。不是设置而是stmt

运行此命令时,您可以看到索引比获取索引快得多。您可以多次运行以查看。

该代码基本上试图获取字典中c的值。

import timeit

print('Getting value of C by index:', timeit.timeit(stmt="mydict['c']", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
print('Getting value of C by get:', timeit.timeit(stmt="mydict.get('c')", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))

这是我的结果,您的结果会有所不同。

按索引:0.20900007452246427

通过获取:0.54841166886888


您正在使用哪个版本的python?
爱德华多

3

只需将整个代码作为timeit的参数传递:

import timeit

print(timeit.timeit(

"""   
limit = 10000
prime_list = [i for i in range(2, limit+1)]

for prime in prime_list:
    for elem in range(prime*2, max(prime_list)+1, prime):
        if elem in prime_list:
            prime_list.remove(elem)
"""   
, number=10))

1
import timeit


def oct(x):
   return x*x


timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()

0

内置的timeit模块在IPython命令行中效果最佳。

要从模块内计时功能:

from timeit import default_timer as timer
import sys

def timefunc(func, *args, **kwargs):
    """Time a function. 

    args:
        iterations=3

    Usage example:
        timeit(myfunc, 1, b=2)
    """
    try:
        iterations = kwargs.pop('iterations')
    except KeyError:
        iterations = 3
    elapsed = sys.maxsize
    for _ in range(iterations):
        start = timer()
        result = func(*args, **kwargs)
        elapsed = min(timer() - start, elapsed)
    print(('Best of {} {}(): {:.9f}'.format(iterations, func.__name__, elapsed)))
    return result

0

如何将Python REPL解释器与接受参数的函数一起使用的示例。

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

0

您将创建两个函数,然后运行与此类似的操作。请注意,您要选择相同的执行/运行次数来比较apple与apple。
这已在Python 3.7下进行了测试。

在此处输入图片说明 这是易于复制的代码

!/usr/local/bin/python3
import timeit

def fibonacci(n):
    """
    Returns the n-th Fibonacci number.
    """
    if(n == 0):
        result = 0
    elif(n == 1):
        result = 1
    else:
        result = fibonacci(n-1) + fibonacci(n-2)
    return result

if __name__ == '__main__':
    import timeit
    t1 = timeit.Timer("fibonacci(13)", "from __main__ import fibonacci")
    print("fibonacci ran:",t1.timeit(number=1000), "milliseconds")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.