我如何使用timeit来比较我自己的函数(如“insertion_sort”和“tim_sort”)的性能?
当前回答
如何使用带有接受参数的函数的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
其他回答
内置的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
让我们在以下每个语句中设置相同的字典并测试执行时间。
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
get: 0.54841166886888
如果你想快速比较两个代码/函数块,你可以这样做:
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)
对我来说,这是最快的方法:
import timeit
def foo():
print("here is my code to time...")
timeit.timeit(stmt=foo, number=1234567)
我告诉你一个秘密:使用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脚本中计时。我建议避免这样做,因为在命令行上进行分析和计时会更好。相反,我倾向于编写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(…),就像我上面展示的那样。
警告
有一些关于时间的警告无处不在。
Overhead is not accounted for. Say you want to time x += 1, to find out how long addition takes: >>> python -m timeit -s "x = 0" "x += 1" 10000000 loops, best of 3: 0.0476 usec per loop Well, it's not 0.0476 µs. You only know that it's less than that. All error is positive. So try and find pure overhead: >>> python -m timeit -s "x = 0" "" 100000000 loops, best of 3: 0.014 usec per loop That's a good 30% overhead just from timing! This can massively skew relative timings. But you only really cared about the adding timings; the look-up timings for x also need to be included in overhead: >>> python -m timeit -s "x = 0" "x" 100000000 loops, best of 3: 0.0166 usec per loop The difference isn't much larger, but it's there. Mutating methods are dangerous. >>> python -m timeit -s "x = [0]*100000" "while x: x.pop()" 10000000 loops, best of 3: 0.0436 usec per loop But that's completely wrong! x is the empty list after the first iteration. You'll need to reinitialize: >>> python -m timeit "x = [0]*100000" "while x: x.pop()" 100 loops, best of 3: 9.79 msec per loop But then you have lots of overhead. Account for that separately. >>> python -m timeit "x = [0]*100000" 1000 loops, best of 3: 261 usec per loop Note that subtracting the overhead is reasonable here only because the overhead is a small-ish fraction of the time. For your example, it's worth noting that both Insertion Sort and Tim Sort have completely unusual timing behaviours for already-sorted lists. This means you will require a random.shuffle between sorts if you want to avoid wrecking your timings.
推荐文章
- 将Pandas或Numpy Nan替换为None以用于MysqlDB
- 使用pandas对同一列进行多个聚合
- 使用Python解析HTML
- django MultiValueDictKeyError错误,我如何处理它
- 如何在for循环期间修改列表条目?
- 我如何在Django中创建一个鼻涕虫?
- 没有名为'django.core.urlresolvers'的模块
- 蟒蛇导出环境文件
- Django - makemigrations -未检测到任何更改
- SQLAlchemy:引擎、连接和会话差异
- 在Python Pandas中删除多个列中的所有重复行
- 更改pandas DataFrame中的特定列名
- 将Pandas多索引转换为列
- 熊猫在每组中获得最高的n个记录
- 熊猫数据帧得到每组的第一行