关于python:如何使用timeit模块

How to use timeit module

我理解timeit的概念,但我不知道如何在我的代码中实现它。

如何将insertion_sorttim_sort这两个函数与timeit进行比较?


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

  • 使用IPython shell。它具有方便的%timeit特殊功能:

    1
    2
    3
    4
    5
    6
    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
  • 在标准的Python解释器中,您可以通过在setup语句中从__main__导入它们来访问先前在交互式会话期间定义的函数和其他名称:

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

  • timeit的工作方式是运行一次安装代码,然后重复调用一系列语句。所以,如果你想测试排序,需要注意一点,这样就地排序的一次传递不会影响已经排序数据的下一次传递(当然,这会让Timsort真正发光,因为它表现最好当数据已经部分订购时)。

    以下是如何设置排序测试的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    >>> 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

    请注意,系列语句在每次传递时都会生成未分类数据的新副本。

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

    这些是我正确使用timeit的提示。希望这可以帮助 :-)


    我将告诉你一个秘密:使用timeit的最佳方法是在命令行上。

    在命令行中,timeit进行适当的统计分析:它告诉您最短的运行时间。这很好,因为时间上的所有错误都是正的。因此,最短的时间内错误最少。没有办法得到负面错误,因为计算机无法计算得比计算速度快!

    所以,命令行界面:

    1
    2
    %~> python -m timeit"1 + 2"
    10000000 loops, best of 3: 0.0468 usec per loop

    那很简单,是吗?

    你可以设置东西:

    1
    2
    %~> python -m timeit -s"x = range(10000)""sum(x)"
    1000 loops, best of 3: 543 usec per loop

    这也很有用!

    如果你想要多行,你可以使用shell的自动延续或使用单独的参数:

    1
    2
    %~> python -m timeit -s"x = range(10000)" -s"y = range(100)""sum(x)""min(y)"
    1000 loops, best of 3: 554 usec per loop

    这给出了一个设置

    1
    2
    x = range(1000)
    y = range(100)

    和时代

    1
    2
    sum(x)
    min(y)

    如果您想要更长的脚本,可能会想要在Python脚本中移动到timeit。我建议避免这种情况,因为在命令行上分析和计时更好。相反,我倾向于制作shell脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     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呢?

    嗯,简单的方法是:

    1
    2
    3
    4
    def function(...):
        ...

    timeit.Timer(function).timeit(number=NUMBER)

    这会给你累计(不是最小!)时间来运行这么多次。

    要获得良好的分析,请使用.repeat并采取最小值:

    1
    min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))

    您通常应将此与functools.partial而不是lambda: ...组合以降低开销。因此你可以有类似的东西:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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

    你也可以这样做:

    1
    timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)

    这会让你从命令行更接近界面,但是以一种不那么酷的方式。 "from __main__ import ..."允许您在timeit创建的人工环境中使用主模块中的代码。

    值得注意的是,这是Timer(...).timeit(...)的便利包装,因此在时间上并不是特别好。我个人更喜欢使用Timer(...).repeat(...),如上所示。

    警告

    timeit有一些警告,到处都有。

    • 不计入间接费用。假设您想要时间x += 1,以了解添加需要多长时间:

      1
      2
      >>> python -m timeit -s"x = 0""x += 1"
      10000000 loops, best of 3: 0.0476 usec per loop

      嗯,它不是0.0476μs。你只知道它不到那个。所有错误都是正面的。

      所以试着找到纯粹的开销:

      1
      2
      >>> python -m timeit -s"x = 0"""      
      100000000 loops, best of 3: 0.014 usec per loop

      从时间开始,这是一个很好的30%开销!这可以大大扭曲相对时间。但你真的很关心增加时间; x的查找时序也需要包含在开销中:

      1
      2
      >>> python -m timeit -s"x = 0""x"
      100000000 loops, best of 3: 0.0166 usec per loop

      差别不大,但它就在那里。

    • 变异方法很危险。

      1
      2
      >>> python -m timeit -s"x = [0]*100000""while x: x.pop()"
      10000000 loops, best of 3: 0.0436 usec per loop

      但这完全错了! x是第一次迭代后的空列表。您需要重新初始化:

      1
      2
      >>> python -m timeit"x = [0]*100000""while x: x.pop()"
      100 loops, best of 3: 9.79 msec per loop

      但是你有很多开销。分别考虑到这一点。

      1
      2
      >>> python -m timeit"x = [0]*100000"                  
      1000 loops, best of 3: 261 usec per loop

      注意,减去开销在这里是合理的,因为开销是时间的一小部分。

      对于您的示例,值得注意的是,Insertion Sort和Tim Sort对已经排序的列表具有完全不同寻常的计时行为。这意味着如果你想避免破坏你的时间,你需要在random.shuffle之间进行排序。


    如果您想快速比较两个代码/函数块,您可以:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import timeit

    start_time = timeit.default_timer()
    func1()
    print(timeit.default_timer() - start_time)

    start_time = timeit.default_timer()
    func2()
    print(timeit.default_timer() - start_time)

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

    鉴于test.py:

    1
    2
    def InsertionSort(): ...
    def TimSort(): ...

    像这样运行timeit:

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

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

    1
    2
    3
    4
    5
    6
    import timeit
    def foo():
        print("here is my code to time...")


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


    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
    # Генерация целых чисел

    def gen_prime(x):
        multiples = []
        results = []
        for i in range(2, x+1):
            if i not in multiples:
                results.append(i)
                for j in range(i*i, x+1, i):
                    multiples.append(j)

        return results


    import timeit

    # Засекаем время

    start_time = timeit.default_timer()
    gen_prime(3000)
    print(timeit.default_timer() - start_time)

    # start_time = timeit.default_timer()
    # gen_prime(1001)
    # print(timeit.default_timer() - start_time)

    这非常有效:

    1
      python -m timeit -c"$(cat file_name.py)"


    让我们在下面的每一个中设置相同的字典并测试执行时间。

    setup参数基本上是设置字典

    Number是运行代码1000000次。不是设置而是stmt

    运行此操作时,您可以看到索引比get快。你可以多次运行它来查看。

    代码基本上试图在字典中获取c的值。

    1
    2
    3
    4
    import timeit

    print('Getting value of C by index:', timeit.timeit(stmt="mydict['c']", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
    print('Getting value of C by get:', timeit.timeit(stmt="mydict.get('c')", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))

    以下是我的结果,您的结果会有所不同。

    按索引:0.20900007452246427

    获取:0.54841166886888


    只需将整个代码作为timeit的参数传递:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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))

    1
    2
    3
    4
    5
    6
    7
    8
    import timeit


    def oct(x):
       return x*x


    timeit.Timer("for x in range(100): oct(x)","gc.enable()").timeit()


    您将创建两个函数,然后运行类似于此的东西。
    注意,你想选择相同数量的执行/运行来比较苹果和苹果。
    这是在Python 3.7下测试的。

    enter image description here
    这是便于复制它的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    !/usr/local/bin/python3
    import timeit

    def fibonacci(n):
       """
        Returns the n-th Fibonacci number.
       """

        if(n == 0):
            result = 0
        elif(n == 1):
            result = 1
        else:
            result = fibonacci(n-1) + fibonacci(n-2)
        return result

    if __name__ == '__main__':
        import timeit
        t1 = timeit.Timer("fibonacci(13)","from __main__ import fibonacci")
        print("fibonacci ran:",t1.timeit(number=1000),"milliseconds")

    如何将Python REPL解释器与接受参数的函数一起使用的示例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    >>> import timeit                                                                                        

    >>> def naive_func(x):                                                                                    
    ...     a = 0                                                                                            
    ...     for i in range(a):                                                                                
    ...         a += i                                                                                        
    ...     return a                                                                                          

    >>> def wrapper(func, *args, **kwargs):                                                                  
    ...     def wrapper():                                                                                    
    ...         return func(*args, **kwargs)                                                                  
    ...     return wrapper                                                                                    

    >>> wrapped = wrapper(naive_func, 1_000)                                                                  

    >>> timeit.timeit(wrapped, number=1_000_000)                                                              
    0.4458435332577161

    内置的timeit模块最适合从IPython命令行运行。

    要在模块内计时功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from timeit import default_timer as timer
    import sys

    def timefunc(func, *args, **kwargs):
       """Time a function.

        args:
            iterations=3

        Usage example:
            timeit(myfunc, 1, b=2)
       """

        try:
            iterations = kwargs.pop('iterations')
        except KeyError:
            iterations = 3
        elapsed = sys.maxsize
        for _ in range(iterations):
            start = timer()
            result = func(*args, **kwargs)
            elapsed = min(timer() - start, elapsed)
        print(('Best of {} {}(): {:.9f}'.format(iterations, func.__name__, elapsed)))
        return result