# 27.5. `timeit` - 测量小代码片段的执行时间

源代码： Lib / timeit.py

本模块提供一个简单的方法来测量小的Python代码的时间。它同时具有Command-Line Interface以及callable的函数。它避免了测量执行时间时大量的常见陷阱。另请参阅O'Reilly出版的Python Cookbook一书中，由Tim Peters编写的"算法"简介一章。

## 27.5.1. Basic Examples

下面的示例演示如何使用Command-Line Interface比较三种不同的表达式：

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

这也可以通过Python Interface实现：

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

请注意，只有使用命令行接口时，`timeit`才会自动确定循环的次数。Examples一节中可以找到更高级的例子。

## 27.5.2. Python Interface

该模块定义了三个便利的函数和用一个公共类：

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

  用给定的statement，setup代码和timer函数创建一个`Timer`实例，然后运行它的`timeit()`方法执行number指定的次数。可选的全局变量参数指定在其中执行代码的命名空间。

  在版本3.5中已更改：添加了可选的全局参数。

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

  用给定的statement，setup代码和timer函数创建一个`Timer`实例，并循环运行`repeat()`方法直到repeat指定的计数，每次计数中代码运行number指定的执行次数。可选的全局变量参数指定在其中执行代码的命名空间。

  在版本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'`；定时器功能是平台相关的（参见模块doc字符串）。stmt和设置也可能包含多个由`;`分隔的语句或换行符，只要它们不包含多行字符串字面值即可。语句将默认在timeit的命名空间中执行；可以通过将命名空间传递到全局变量来控制此行为。

  若要测量的第一个语句的执行时间，请使用`timeit()`方法。`repeat()`方法是多次调用`timeit()`的一种便利方法，并返回结果的列表。

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

  stmt和设置参数也可以使用无参数可调用的对象。这将在随后由`timeit()`执行的定时器函数中嵌入对它们的调用。请注意在这种情况下由于额外的函数调用时间开销会有点大。

  在版本3.5中已更改：添加了可选的全局参数。

  `timeit`(number=1000000)

    计时主要语句执行number次的时间。它将执行一次setup语句，返回执行主要语句执行多次所需的时间，以浮点数秒数表示。参数为循环的次数，默认是100万。要用的主语句、setup语句和计时器函数将传递给构造函数。

    注

    默认情况下，在计时期间`timeit()`暂时关闭garbage collection。这种方法的优点是它使得独立的计时更具有可比性。缺点是垃圾回收可能被测量函数性能的一个重要组成部分。如果是这样，垃圾回收可以利用setup字符串的第一个语句重新启用。举个例子：

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

  `repeat`(repeat=3, number=1000000)

    调用`timeit()`多次。

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

    注

    从结果向量它很容易计算平均值和标准偏差，并报告这些。然而，这不是很有用的。在典型情况下，最低值给出了您的机器运行给定代码片段的速度的下限；结果向量中较高的值通常不是由Python的速度变化引起的，而是由其他进程干扰您的时序精度造成的。所以结果中的`min()`可能你应该感兴趣的唯一一个数。在那之后，你应该看看整个矢量并应用常识而不是统计数据。

  `print_exc`(file=None)

    从被计时的代码打印回溯的helper。

    典型的使用：

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

    相比标准的回溯的优点是将显示编译后的模板中的源代码行。可选的文件参数指示发送回溯的位置；它默认为`sys.stderr`。

## 27.5.3. Command-Line Interface

当作为一个程序从命令行调用时，使用以下格式：

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

下面是选项的解释：

`-n` `N``,` `--number``=N`

  执行'statement'多少次

`-r` `N``,` `--repeat``=N`

  重复计时器多少次（默认为3）

`-s` `S``,` `--setup``=S`

  一旦初识化后要执行的语句（默认为`pass`）

`-p````,` `--process```

  测量处理时间，而不是时钟时间，使用`time.process_time()`而不是`time.perf_counter()`

  版本3.3中的新功能。

`-t````,` `--time```

  使用`time.time()`（已弃用）

`-u````,` `--unit``=U`

  > specify a time unit for timer output; can select usec, msec, or sec

  版本3.5中的新功能。

`-c````,` `--clock```

  使用`time.clock()`（已弃用）

`-v````,` `--verbose```

  打印原始时序结果；重复以获得更多数字精度

`-h````,` `--help```

  打印一个短的用法信息并退出

多行语句可以通过将每一行指定为单独的语句参数来给出；缩进线可以通过使用引号包围参数并使用前导空格。多个`-s`选项的处理方式类似。

如果不指定`-n` ，适当数目的循环计算的尝试连续的 10 的幂，直到总的时间为至少 0.2 秒。

`default_timer()`测量可能会受到在同一台机器上运行的其他程序的影响，因此，当需要准确计时时，最好的做法是重复计时几次，并使用最佳时间。`-r`选项对此有好处；在大多数情况下默认为3次重复可能就足够了。您可以使用`time.process_time()`来测量CPU时间。

注

还有与执行 pass 语句相关联的某些基线系统开销。这里的代码不会尝试隐藏它，但是你应该意识到这一点。可以通过调用不带参数，程序测量基线开销和 Python 版本之间可能有差异。

## 27.5.4. Examples

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

    $ python -m timeit -s 'text = "sample string"; char = "g"'  'char in text'
    10000000 loops, best of 3: 0.0877 usec per loop
    $ python -m timeit -s 'text = "sample string"; char = "g"'  'text.find(char)'
    1000000 loops, best of 3: 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.40193588800002544, 0.3960157959998014, 0.39594301399984033]

下面的示例演示如何计时包含多行的表达式。在这里我们比较使用`hasattr()`与`try`/`except`来测试对象缺失和存在的属性：

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

    $ python -m timeit 'try:' '  int.__bool__' 'except AttributeError:' '  pass'
    1000000 loops, best of 3: 1.43 usec per loop
    $ python -m timeit 'if hasattr(int, "__bool__"): pass'
    100000 loops, best of 3: 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`模块访问给您定义的函数的权限，可以通过传递setup参数，其中包含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()`传递到全局参数，这将导致代码在当前全局命名空间中执行。这可以比单独指定导入更方便：

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