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

源代码: Lib/timeit.py


该模块提供了一种计时少量 Python 代码的简单方法。它既有Command-Line Interface也有callable。它避免了许多用于测量执行时间的常见陷阱。另请参见 O'Reilly 出版的* Python Cookbook *中 Tim Peters 对“算法”一章的介绍。

Basic Examples

以下示例显示了如何使用Command-Line Interface比较三个不同的表达式:

$ 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

可以passPython Interfacepass以下方式实现:

>>> 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 Interface传递可呼叫对象:

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

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

Python Interface

该模块定义了三个便捷Function和一个公共类:

  • timeit. timeit(* stmt ='pass' setup ='pass' timer = number = 1000000 globals = None *)
    • 使用给定的语句,* setup 代码和 timer 函数创建一个Timer实例,并pass number 次执行来运行其timeit()方法。可选的 globals *参数指定在其中执行代码的名称空间。

在版本 3.5 中更改:添加了可选的* globals *参数。

  • timeit. repeat(* stmt ='pass' setup ='pass' timer = repeat = 5 number = 1000000 globals = None *)
    • 使用给定的语句,* setup 代码和 timer 函数创建一个Timer实例,并使用给定的 repeat * count 和* number 执行来运行其repeat()方法。可选的 globals *参数指定在其中执行代码的名称空间。

在版本 3.5 中更改:添加了可选的* globals *参数。

在 3.7 版中更改:“重复” *的默认值从 3 更改为 5.

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

    • class * timeit. Timer(* stmt ='pass' setup ='pass' timer = globals = None *)
    • 用于计时小代码段的执行速度的类。

构造函数接受一条要计时的语句,一条用于设置的附加语句以及一个计时器函数。这两个语句默认为'pass';计时器Function取决于平台(请参阅模块文档字符串)。 * stmt setup 也可以包含多个由;或换行符分隔的语句,只要它们不包含多行字符串 Literals 即可。默认情况下,该语句将在其名称空间中执行;可以pass将名称空间传递给 globals *来控制此行为。

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

  • setup *的执行时间不包括在整体定时执行运行中。

  • stmt setup *参数还可以采用无需参数即可调用的对象。这会将对它们的调用嵌入到计时器函数中,然后由timeit()执行。请注意,在这种情况下,由于额外的函数调用,时序开销会稍大一些。

在版本 3.5 中更改:添加了可选的* globals *参数。

  • timeit(* number = 1000000 *)
    • 主语句的时间数量执行。这将一次执行 setup 语句,然后返回多次执行主语句所需的时间,以秒为单位,以浮点数为单位。参数是循环的次数,默认为一百万次。将要使用的主语句,设置语句和计时器函数传递给构造函数。

Note

默认情况下,timeit()在计时期间暂时关闭garbage collection。这种方法的优势在于,它使独立计时更具可比性。缺点是 GC 可能是被测Function性能的重要组成部分。如果是这样,可以将 GC 作为* setup *字符串中的第一条语句重新启用。例如:

timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit()
  • autorange(* callback = None *)
    • 自动确定要呼叫timeit()的次数。

这是一个便捷函数,它反复调用timeit(),使得总时间> = 0.2 秒,并返回finally值(循环数,该循环数所花费的时间)。它会从序列 1、2、5、10、20、50 ...递增编号调用timeit(),直到花费的时间至少为 0.2 秒。

如果给出* callback *而不是None,则每次try后将使用两个参数callback(number, time_taken)进行调用。

3.6 版的新Function。

  • repeat(* repeat = 5 number = 1000000 *)

这是一个便捷函数,它反复调用timeit()并返回结果列表。第一个参数指定调用timeit()的次数。第二个参数指定timeit()的* number *参数。

Note

试图根据结果向量计算均值和标准差并报告这些值很诱人。但是,这不是很有用。在典型情况下,最小值给出了机器可以运行给定代码段的速度的下限;结果向量中较高的值通常不是由 Python 速度的可变性引起的,而是由其他影响您计时精度的过程引起的。因此,结果min()可能是您应该感兴趣的唯一数字。此后,您应该查看整个向量,并应用常识而不是统计学。

在 3.7 版中更改:“重复” *的默认值从 3 更改为 5.

  • print_exc(* file = None *)
    • 帮助程序从定时代码中打印回溯。

Typical use:

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

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

Command-Line Interface

从命令行作为程序调用时,使用以下形式:

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

了解以下选项的地方:

  • -n N , --number =N

    • 多少次执行“语句”
  • -r N , --repeat =N

    • 重复多少次计时器(默认 5)
  • -s S , --setup =S

    • 语句最初要执行一次(默认pass)
  • -p `,` `--process`

版本 3.3 中的新Function。

-u `` , --unit =U

Note

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

3.5 版中的新Function。

  • -v `,` `--verbose`

    • 打印原始计时结果;重复以提高数字精度
  • -h `,` `--help`

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

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

如果未给出-n,则passtry从序列 1、2、5、10、20、50 等增加数字直到总时间至少为 0.2 秒,来计算合适的循环数。

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

Note

执行 pass 语句有一定的基线开销。此处的代码不会try隐藏它,但是您应该意识到这一点。基线开销可以pass不带参数的程序来测量,并且在两个 Python 版本之间可能有所不同。

Examples

可以提供一个开始仅执行一次的 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()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模块访问您定义的Function,可以传递一个包含导入语句的* setup *参数:

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