timeit ---测量小代码段的执行时间

源代码: Lib/timeit.py


这个模块提供了一种简单的方法来计时少量的python代码。它都有 命令行界面 以及A callable 一个。它避免了许多测量执行时间的常见陷阱。另请参见Tim Peters的“算法”介绍 * Python 秘诀* 由O'Reilly出版。

基本实例

下面的示例显示了 命令行界面 可用于比较三种不同的表达式:

$ python3 -m timeit '"-".join(str(n) for n in range(100))'
10000 loops, best of 5: 30.2 usec per loop
$ python3 -m timeit '"-".join([str(n) for n in range(100)])'
10000 loops, best of 5: 27.5 usec per loop
$ python3 -m timeit '"-".join(map(str, range(100)))'
10000 loops, best of 5: 23.2 usec per loop

这可以通过 python接口 用:

>>> import timeit
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
0.3018611848820001
>>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000)
0.2727368790656328
>>> timeit.timeit('"-".join(map(str, range(100)))', number=10000)
0.23702679807320237

也可以从 python接口 ::

>>> timeit.timeit(lambda: "-".join(map(str, range(100))), number=10000)
0.19665591977536678

但是请注意 timeit() 只有在使用命令行界面时才会自动确定重复次数。在 实例 您可以找到更高级的示例。

python接口

该模块定义了三个便利功能和一个公共类:

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

创建一个 Timer 具有给定语句的实例, 设置 代码和 计时器 函数并运行其 timeit() 方法与 处决。可选的 globals 参数指定要在其中执行代码的命名空间。

在 3.5 版更改: 可选的 globals 已添加参数。

timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)

创建一个 Timer 具有给定语句的实例, 设置 代码和 计时器 函数并运行其 repeat() 给定的方法 重复 伯爵 处决。可选的 globals 参数指定要在其中执行代码的命名空间。

在 3.5 版更改: 可选的 globals 已添加参数。

在 3.7 版更改: 的默认值 重复 从3改为5。

timeit.default_timer()

默认计时器,它始终 time.perf_counter() .

在 3.3 版更改: time.perf_counter() 现在是默认计时器。

class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)

类,用于小代码段的计时执行速度。

构造函数接受一个要定时的语句、一个用于安装的附加语句和一个计时器函数。两个语句都默认为 'pass' ;计时器功能依赖于平台(参见模块文档字符串)。 stmt设置 也可以包含多个语句,用分隔符分隔 ; 或者换行,只要它们不包含多行字符串。默认情况下,语句将在TimeIt的命名空间内执行;可以通过将命名空间传递给 globals .

要测量第一条语句的执行时间,请使用 timeit() 方法。这个 repeat()autorange() 方法是方便调用的方法 timeit() 多次。

执行时间 设置 从整个定时执行运行中排除。

这个 stmt设置 参数也可以接受不带参数的可调用对象。这将在计时器函数中嵌入对它们的调用,然后由 timeit() . 注意,在这种情况下,由于额外的函数调用,时间开销要大一些。

在 3.5 版更改: 可选的 globals 已添加参数。

timeit(number=1000000)

时间 执行主语句。这将执行一次SETUP语句,然后返回执行主语句所需的时间(以秒为单位的浮点数)。参数是通过循环的次数,默认为一百万次。将要使用的主语句、安装语句和计时器函数传递给构造函数。

注解

默认情况下, timeit() 暂时关闭 garbage collection 在计时期间。这种方法的优点是它使独立的时间安排更具可比性。缺点是GC可能是被测函数性能的重要组成部分。如果是这样,则可以将gc作为 设置 字符串。例如::

timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
autorange(callback=None)

自动确定要调用的次数 timeit() .

这是一个调用 timeit() 重复,使总时间大于等于0.2秒,返回最终(循环数,循环数所用的时间)。它叫 timeit() 随着序列1、2、5、10、20、50中的数字增加,…直到所用时间至少为0.2秒。

如果 回调 是给予而不是 None ,在每次审判后,将用两个参数调用它: callback(number, time_taken) .

3.6 新版功能.

repeat(repeat=5, number=1000000)

调用 timeit() 几次。

这是一个方便函数,它调用 timeit() 重复返回结果列表。第一个参数指定调用多少次 timeit() . 第二个参数指定 的参数 timeit() .

注解

从结果向量中计算平均值和标准偏差并报告它们是很有诱惑力的。然而,这并不是很有用。在典型的情况下,最低值给出了机器运行给定代码段的速度的下限;结果向量中较高的值通常不是由Python速度的可变性造成的,而是由其他影响计时准确性的进程造成的。所以 min() 结果可能是你唯一感兴趣的数字。在这之后,您应该查看整个向量,并应用常识而不是统计信息。

在 3.7 版更改: 的默认值 重复 从3改为5。

print_exc(file=None)

帮助打印来自定时代码的跟踪。

典型用途:

t = Timer(...)       # outside the try/except
try:
    t.timeit(...)    # or t.repeat(...)
except Exception:
    t.print_exc()

与标准回溯相比,优势在于将显示已编译模板中的源代码行。可选的 file 参数指示回溯的发送位置;默认为 sys.stderr .

命令行界面

当从命令行作为程序调用时,将使用以下表单:

python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]

如果理解以下选项:

-n N, --number=N

执行“statement”的次数

-r N, --repeat=N

重复计时器的次数(默认为5次)

-s S, --setup=S

最初执行一次的语句(默认 pass

-p, --process

测量过程时间,而不是WallClock时间,使用 time.process_time() 而不是 time.perf_counter() ,这是默认值

3.3 新版功能.

-u, --unit=U

为计时器输出指定时间单位;可以选择nsec、usec、msec或sec

3.5 新版功能.

-v, --verbose

打印原始计时结果;重复以获得更高的数字精度

-h, --help

打印简短的使用信息并退出

多行语句可以通过将每一行指定为单独的语句参数来给出;缩进行可以通过将参数括在引号中并使用前导空格来实现。倍数 -s 选项的处理方式类似。

如果 -n 如果没有给出,则通过尝试从序列1、2、5、10、20、50中增加数字来计算适当数量的循环。。。直到总时间至少为0.2秒。

default_timer() 测量可能会受到同一台机器上运行的其他程序的影响,因此,当需要精确计时时,最好的做法是重复计时几次,并使用最佳时间。这个 -r 选项是很好的;在大多数情况下,5次重复的默认值可能就足够了。你可以使用 time.process_time() 测量CPU时间。

注解

有一个与执行一个pass语句相关联的基线开销。这里的代码不会试图隐藏它,但您应该知道它。基线开销可以通过在没有参数的情况下调用程序来衡量,并且在不同的Python版本之间可能会有所不同。

实例

可以提供只在开始时执行一次的SETUP语句:

$ python -m timeit -s 'text = "sample string"; char = "g"'  'char in text'
5000000 loops, best of 5: 0.0877 usec per loop
$ python -m timeit -s 'text = "sample string"; char = "g"'  'text.find(char)'
1000000 loops, best of 5: 0.342 usec per loop
>>> import timeit
>>> timeit.timeit('char in text', setup='text = "sample string"; char = "g"')
0.41440500499993504
>>> timeit.timeit('text.find(char)', setup='text = "sample string"; char = "g"')
1.7246671520006203

也可以使用 Timer 类及其方法:

>>> import timeit
>>> t = timeit.Timer('char in text', setup='text = "sample string"; char = "g"')
>>> t.timeit()
0.3955516149999312
>>> t.repeat()
[0.40183617287970225, 0.37027556854118704, 0.38344867356679524, 0.3712595970846668, 0.37866875250654886]

以下示例演示如何对包含多行的表达式计时。这里我们比较一下使用成本 hasattr() vs. try/except 要测试缺少和显示的对象属性:

$ python -m timeit 'try:' '  str.__bool__' 'except AttributeError:' '  pass'
20000 loops, best of 5: 15.7 usec per loop
$ python -m timeit 'if hasattr(str, "__bool__"): pass'
50000 loops, best of 5: 4.26 usec per loop

$ python -m timeit 'try:' '  int.__bool__' 'except AttributeError:' '  pass'
200000 loops, best of 5: 1.43 usec per loop
$ python -m timeit 'if hasattr(int, "__bool__"): pass'
100000 loops, best of 5: 2.23 usec per loop
>>> import timeit
>>> # attribute is missing
>>> s = """\
... try:
...     str.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.9138244460009446
>>> s = "if hasattr(str, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.5829014980008651
>>>
>>> # attribute is present
>>> s = """\
... try:
...     int.__bool__
... except AttributeError:
...     pass
... """
>>> timeit.timeit(stmt=s, number=100000)
0.04215312199994514
>>> s = "if hasattr(int, '__bool__'): pass"
>>> timeit.timeit(stmt=s, number=100000)
0.08588060699912603

给予 timeit 模块访问您定义的函数,可以通过 设置 包含import语句的参数:

def test():
    """Stupid test function"""
    L = [i for i in range(100)]

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

另一个选择是通过 globals()globals 参数,这将导致代码在当前全局命名空间中执行。这比单独指定导入更方便:

def f(x):
    return x**2
def g(x):
    return x**4
def h(x):
    return x**8

import timeit
print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))