如何使用timeit模块
Posted
技术标签:
【中文标题】如何使用timeit模块【英文标题】:How to use timeit module 【发布时间】:2012-01-03 11:34:53 【问题描述】:我了解timeit
的概念,但我不确定如何在我的代码中实现它。
我如何比较两个函数,比如insertion_sort
和tim_sort
,与timeit
?
【问题讨论】:
【参考方案1】:我发现使用 timeit 最简单的方法是从命令行:
给定 test.py:
def InsertionSort(): ...
def TimSort(): ...
像这样运行时间:
% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'
【讨论】:
【参考方案2】: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 的技巧。希望这会有所帮助:-)
【讨论】:
是的,它包括列表副本(与排序本身相比非常快)。但是,如果您不复制,则第一遍对列表进行排序,其余通过的不必做任何工作。如果您想知道排序的时间,那么在有和没有timsort(a)
的情况下运行上面的代码并取不同:-)
@max 使用 min() 而不是平均时间。这是我、Tim Peters 和 Guido van Rossum 的建议。最快的时间代表当缓存被加载并且系统不忙于其他任务时算法可以执行的最佳时间。所有的时间都是嘈杂的——最快的时间是最不嘈杂的。很容易证明,最快的计时是最可重复的,因此在计时两个不同的实现时最有用。
您计算 1000 个输入的 平均值(嗯,总计,但它是等价的);然后重复7次,取最小值。您需要平均超过 1000 个输入,因为您需要平均(不是最佳情况)算法复杂度。由于您给出的原因,您需要最低限度。我想我可以通过选择一个输入来改进你的方法,运行算法 7 次,取最小值;然后对 1000 个不同的输入重复它,并取平均值。我没有意识到您的.repeat(7,1000)
已经这样做了(通过使用相同的种子)!所以你的解决方案是完美的 IMO。
我只能补充一点,您如何分配 7000 次执行的预算(例如,.repeat(7, 1000)
与 .repeat(2, 3500)
与 .repeat(35, 200
)应该取决于系统负载导致的错误与输入可变性导致的误差。在极端情况下,如果您的系统始终处于高负载状态,并且您在执行时间分布的左侧看到一条细长的尾巴(当您发现它处于罕见的空闲状态时),您甚至可能会发现 .repeat(7000,1)
更有用如果您的预算不能超过 7000 次运行,则不要超过 .repeat(7,1000)
。
如何复制已经在设置中的数组,在它们之上创建一个迭代器it
,然后计时'a=next(it); timsort(a)'
?【参考方案3】:
如果您想在交互式 Python 会话中使用 timeit
,有两个方便的选项:
使用IPython 外壳。它具有方便的%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
在标准 Python 解释器中,您可以访问之前在交互式会话期间定义的函数和其他名称,方法是从 setup 语句中的 __main__
导入它们:
>>> 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]
【讨论】:
+1 用于展示from __main__ import f
技术。我认为这并不像它应该的那样广为人知。在这样的情况下,函数或方法调用正在计时,它很有用。在其他情况下(对一系列步骤进行计时),它的用处不大,因为它会引入函数调用开销。
你可以做%timeit f(x)
注意:“import f”设置使对 f 的访问成为快速本地读取 - 这并不完全反映典型正常代码中的全局函数调用(短快速函数)。在 Py3.5+ 中可以提供真正的全局变量:“3.5 版更改:添加了可选的全局参数。”;在不可避免的 timeit 模块的全局变量之前(这没有多大意义)。可能调用代码的全局变量 (sys._getframe(N).f_globals
) 从一开始就应该是默认值。【参考方案4】:
告诉你一个秘密:使用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
这也很有用!
如果你想要多行,你可以使用 shell 的自动延续或使用单独的参数:
%~> 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)
如果您想要更长的脚本,您可能会想在 Python 脚本中移动到 timeit
。我建议避免这种情况,因为在命令行上的分析和时间安排更好。相反,我倾向于制作 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
请注意,这里减去开销是合理的只是因为开销只是时间的一小部分。
对于您的示例,值得注意的是 插入排序和 Tim 排序对于已经排序的列表具有完全不寻常的计时行为。这意味着如果您想避免破坏您的时间安排,您将需要在排序之间使用random.shuffle
。
【讨论】:
usec 是什么意思?是微秒吗? @HasanIqbalAnik 是的。 @StefanPochmann 因为它不会尝试多次采样。 这个答案的读者可能也对Use Python'stimeit
from a program but functioning the same way as the command line?感兴趣。
@Veedrac 考虑到您关于减去纯计时开销的声明,timeit
在没有给出参数时执行pass
语句,这当然需要一些时间。如果给出任何参数,pass
将不会被执行,因此从每个时间中减去一些 0.014
使用秒是不正确的。【参考方案5】:
如果您想快速比较两个代码/功能块,您可以这样做:
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)
【讨论】:
【参考方案6】:让我们在下面的每一个中设置相同的字典并测试执行时间。
setup 参数基本上是设置字典
数字是运行代码 1000000 次。不是设置而是stmt
当你运行它时,你可以看到 index 比 get 快得多。可以多次运行查看。
代码基本上是试图获取字典中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?【参考方案7】:# Генерация целых чисел
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)
【讨论】:
【参考方案8】:内置的 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
【讨论】:
【参考方案9】:如何将 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
【讨论】:
语法错误:位置参数跟在关键字参数之后。从 REPL 以及单独的文件中运行它。 @VladBezden,timeit.timeit(lambda: naive_func(x), number=1_000_000)
有什么不同吗?【参考方案10】:
这很好用:
python -m timeit -c "$(cat file_name.py)"
【讨论】:
什么是 Windows 等价物? 如果脚本需要参数,你如何传递参数?【参考方案11】:只需将整个代码作为 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))
【讨论】:
【参考方案12】:对我来说,这是最快的方式:
import timeit
def foo():
print("here is my code to time...")
timeit.timeit(stmt=foo, number=1234567)
【讨论】:
【参考方案13】:您将创建两个函数,然后运行与此类似的东西。 请注意,您要选择相同的执行/运行次数来比较苹果。 这是在 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")
【讨论】:
【参考方案14】:import timeit
def oct(x):
return x*x
timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()
【讨论】:
什么是gc.enable()
?
激活在这些计时运行期间通常停用的Garbage Collection
。以上是关于如何使用timeit模块的主要内容,如果未能解决你的问题,请参考以下文章