• Python timeit库测试代码片段运行时间


    timeit库文档:https://docs.python.org/zh-cn/3/library/timeit.html


    timeit 是一个用来测量小代码片段执行时间的工具库,有命令行函数调用两种方法可供使用。

    可以直接查看文档最后的示例部分快速了解其用法。
    注意

    • 使用函数调用时,timeit()方法的返回值以秒为单位,而且返回的是代码语句运行number次(timeit方法的参数)的总耗时,不是多次运行取最好结果/平均结果。
    • 在在命令行中,使用timeit语句返回结果是多次循环所花费的平均时间,时间单位可以指定。

    基本示例:

    需要注意的是,在命令行,传给timeit 的 stmt 参数(即要运行的测试代码)都要是字符串格式的,而不是函数引用或Python语句。在函数中,除了字符串,可以传入一个可调用对象。

    1. 命令行方式:
      如果是在windows下使用命令行执行语句,切记最外层需要使用双引号,否则会报错。

      $ 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
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. 函数调用:

      >>> 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
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      在函数调用时,可以传入可调用对象:

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

    但是请注意,timeit ()只有在使用命令行界面时才会自动确定重复次数。


    Python接口(函数调用)的使用方法:

    模块包含3个常用方法和一个Timer类:

    • timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)
      使用给定的stmt、setup代码和timer函数创建一个 Timer 实例,并根据number运行它的 timeit ()方法。可选的 globals 参数指定在其中执行代码的命名空间。

    • timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)
      使用给定的stmt和setup代码和timer函数创建一个 Timer 实例,并使用给定的repeat计数和number执行运行它的 repeat ()方法(默认执行5 * 1000000次)。可选的 globals 参数指定在其中执行代码的命名空间。

    • timeit.default_timer()
      默认的计时器 time.perf_counter().

    • *class* timeit.Timer(stmt='pass', setup='pass', timer=<timer function>, globals=None)
      用于计时小代码端执行速度的类。该类包括timeit(), autorange(), repeat()和print_exec()四个方法。

      构造函数接受一条要计时的语句stmt、一条用于设置的附加语句setup和一个计时器函数。这两个语句都默认为“ pass”; 计时器函数依赖于平台。Stmt 和 setup 还可以包含由; 或换行分隔的多个语句,只要它们不包含多行字符串文字。默认情况下,语句将在其名称空间内执行; 可以通过将名称空间传递给globals变量来控制此行为。

      若要度量第一个语句stmt的执行时间,请使用 timeit ()方法。Repeat ()和 autOrange ()方法是方便多次调用 timeit ()的方法。setup语句的执行时间被排除在整个计时执行运行之外。

      Stmt 和 setup 参数还可以接受不带参数的可调用对象(callable)。这将把对它们的调用嵌入到一个计时器函数中,然后由 timeit ()执行。注意,在这种情况下,由于额外的函数调用,计时开销有点大。

      • timeit(number=1000000)
        主语句的执行时间。这会执行 setup 语句一次,然后返回执行主语句若干次所需的时间,以秒为单位作为浮点数进行计算。参数是通过循环的次数,默认为100万次。主语句、 setup 语句和要使用的 timer 函数被传递给构造函数。

        注意:在使用timeit()函数测试时,默认会临时关闭Python的垃圾回收功能。这种方法的优点是它使独立计时更具可比性。缺点是 GC 可能是被测量函数性能的重要组成部分。如果是这样,GC 可以作为设置字符串中的第一条语句重新启用。 timeit.Timer('for i in range(10): oct(i)', 'gc.enable()').timeit() 在创建Timer对象时的stmt中设置gc.enable()

      • autorange(callback=None)
        自动确定调用 timeit ()的次数。
        这是一个便捷函数,用来反复调用 timeit () ,使总时间 > = 0.2秒,返回最终结果(循环次数和循环花费的时间)。它使用序列1、2、5、10.20、50中的递增数字调用 timeit () ,直到所花费的时间至少为0.2秒。
        如果回调callback是给定的,并且不是 None,那么在每次试验之后将使用两个参数调用它: callback (number,time_taken)

      • repeat(repeat=5, number=1000000)
        调用计时器repeat次。
        这是一个便捷函数,它重复调用 timeit () ,返回一个结果列表。第一个参数repeat指定调用 timeit ()的次数。第二个参数指定 timeit ()的 number 参数。
        注意:从结果向量计算平均值和标准差并报告这些结果是很有诱惑力的。然而,这并不是很有用。在一个典型的例子中,最低值给出了您的计算机运行给定代码片段的速度的下限; 结果向量中较高的值通常不是由 Python 速度的变化引起的,而是由于其他进程干扰了计时的准确性。因此,结果的 min ()可能是您应该感兴趣的唯一数字。之后,您应该用常识看待整个向量而不是从统计的角度看待整个结果。

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

        t = Timer(...)       # outside the try/except
        try:
            t.timeit(...)    # or t.repeat(...)
        except Exception:
            t.print_exc()
        
        • 1
        • 2
        • 3
        • 4
        • 5

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


    命令行接口:

    python -m timeit [-n N] [-r N] [-u U] [-s S] [-h] [statement ...]
    -n timer执行statement的次数
    -r 重复执行timer的次数,默认为5
    -u 指定timer输出的时间格式,可以指定为:nsec、usec、msec、sec
    -s setup 语句,默认为pass
    -p 使用time.process_time()而不是默认的time.perf_counter()来度量进程时间,而不是墙钟时间
    -v 详细信息,打印原始的计时结果
    -h 帮助信息
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    一个多行的statement可以通过使用 ; 分给为多个语句,带缩进的行可以使用引号括起一个参数并使用前导空格。-s选项的多行语法是类似的。


    示例:

    $ 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
    
    • 1
    • 2
    • 3
    • 4

    使用-s 指定的setup语句只在开始时执行一次。
    在输出中有三个字段:

    • loop count:告诉您每次计时循环重复运行语句体的次数。
    • repetition count:(best of 5)告诉您计时循环重复的次数。
    • 平均耗时:语句主体在计时循环的最佳重复范围内所花费的平均时间。

    以上语句使用函数和Timer类的代码如下:

    >>> 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    >>> 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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    多行语句:

    当statement语句中包含多行语句时,在命令行和Python代码中可以如下执行:

    $ 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    >>> 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    测试函数运行时间:

    当要对我们定义的函数进行时间测量时,可以通过setup参数传递脚本中需要的 import 语句(即使是在当前脚本下也要import:from __main__ import func),stmt 参数传递方法调用字符串化后的表达式:

    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"))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    另一种方法是向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()))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    函数调用与命令行调用返回结果的差异:

    • 函数调用中timeit()方法的返回值,单位为秒,而且返回的是运行number次的总用时,不是平均用时。
    • 命令行中返回的结果是循环的平均用时,单位可以自行指定。
    In [2]: def test():
       ...:     a = 0
       ...:     for _ in range(1000):
       ...:         a += _
       ...: 
    
    In [3]: timeit.timeit('test()', globals=globals())
    Out[3]: 35.442787599982694
    
    In [4]: timeit.timeit('test()', number=1, globals=globals())
    Out[4]: 3.50000336766243e-05
    
    In [5]: timeit.timeit('test()', number=10, globals=globals())
    Out[5]: 0.00033359997905790806
    
    In [6]: timeit.timeit('test()', number=100, globals=globals())
    Out[6]: 0.0032857999904081225
    
    In [7]: timeit.timeit('test()', number=1000, globals=globals())
    Out[7]: 0.03538430004846305
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    >python -m timeit "a=0" "for _ in range(1000):" "  a+=_" 
    10000 loops, best of 5: 32.4 usec per loop
    # 以秒为单位
    >python -m timeit -u sec "a=0" "for _ in range(1000):" "  a+=_" 
    10000 loops, best of 5: 3.24e-05 sec per loop
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    SpringBoot配置文件(学习笔记)
    SQL分类
    前端面试题
    多线程的学习01
    flink sql clinet 实战:upsert kafka connector -- flink-1.12
    go语言学习-基本概念与流程控制
    隐私交易成新刚需,Unijoin 凭什么优势杀出重围?
    环境影响评价期末复习
    【接口测试】Jmeter接口实战-Dubbo接口+造10W数据测试(详细)
    【pwn入门】使用python打二进制
  • 原文地址:https://blog.csdn.net/weixin_43863487/article/details/125456152