我如何使用timeit来比较我自己的函数(如“insertion_sort”和“tim_sort”)的性能?


当前回答

我告诉你一个秘密:使用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.

其他回答

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

鉴于test.py:

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

像这样运行timeit:

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

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

请注意,这一系列语句在每次传递时都对未排序的数据进行新拷贝。

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

以上就是我对如何正确利用时间的建议。

简单地传递你的整个代码作为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))

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

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


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

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

Use the IPython shell. It features the convenient %timeit special function: 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 In a standard Python interpreter, you can access functions and other names you defined earlier during the interactive session by importing them from __main__ in the setup statement: >>> 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]