【问题标题】:Accurate timing of functions in pythonpython中函数的准确计时
【发布时间】:2010-10-27 18:08:18
【问题描述】:

我正在 windows 上使用 python 编程,并希望准确测量函数运行所需的时间。我写了一个函数“time_it”,它接受另一个函数,运行它,并返回它运行所用的时间。

def time_it(f, *args):
    start = time.clock()
    f(*args)
    return (time.clock() - start)*1000

我调用了 1000 次并将结果取平均值。 (末尾的 1000 常量是在毫秒内给出答案。)

这个函数似乎可以工作,但我有一种烦人的感觉,我做错了什么,而且这样做我使用的时间比函数运行时实际使用的时间要多。

有没有更标准或更可接受的方式来做到这一点?

当我将测试函数更改为调用打印以使其花费更长的时间时,我的 time_it 函数返回平均 2.5 毫秒,而 cProfile.run('f()') 返回平均 7.0 毫秒。我认为我的函数会高估时间,如果有的话,这是怎么回事?

另外一点,我关心的是功能之间的相对时间,而不是绝对时间,因为这显然会因硬件和其他因素而异。

【问题讨论】:

    标签: python testing time profiling


    【解决方案1】:

    使用 Python 标准库中的 timeit module

    基本用法:

    from timeit import Timer
    
    # first argument is the code to be run, the second "setup" argument is only run once,
    # and it not included in the execution time.
    t = Timer("""x.index(123)""", setup="""x = range(1000)""")
    
    print t.timeit() # prints float, for example 5.8254
    # ..or..
    print t.timeit(1000) # repeat 1000 times instead of the default 1million
    

    【讨论】:

    • 我希望我的函数被不同的参数调用,但是当我调用 t = timeit.Timer("f()", "from main 使用不同的论点导入 f") 并再次运行 t.timeit(10000),我得到相同的结果,尽管不同的论点应该导致非常不同的运行时。
    【解决方案2】:

    建议您查看内置的 Python 分析器(profilecProfile,取决于您的需要),而不是编写自己的分析代码:http://docs.python.org/library/profile.html

    【讨论】:

    • 忽略我 - 该字符串不是函数名,它是一段 eval 代码。所以你可以用它来快速计时。这是正确的答案。在其他新闻中——“不是”比“!=”快得多——但可能有其他含义。
    • 在切线上关闭 - 在使用“不是”之前 - 记住这一点 - stackoverflow.com/questions/1392433/…
    • 然而,python 配置文件文档说:“分析器模块旨在为给定程序提供执行配置文件,而不是用于基准测试目的(为此,存在合理准确结果的 timeit)。”
    【解决方案3】:

    你可以像这样创建一个“timeme”装饰器

    import time                                                
    
    def timeme(method):
        def wrapper(*args, **kw):
            startTime = int(round(time.time() * 1000))
            result = method(*args, **kw)
            endTime = int(round(time.time() * 1000))
    
            print(endTime - startTime,'ms')
            return result
    
        return wrapper
    
    @timeme
    def func1(a,b,c = 'c',sleep = 1):
        time.sleep(sleep)
        print(a,b,c)
    
    func1('a','b','c',0)
    func1('a','b','c',0.5)
    func1('a','b','c',0.6)
    func1('a','b','c',1)
    

    【讨论】:

    • +n 这个答案。我希望有这样的选择。美是我可以将日志结果导出到外部文件并添加到我需要的任何地方!非常感谢。
    • 这适用于非递归函数。对于递归函数,它返回函数每次迭代的时间。
    • 这里是更精致的版本:stackoverflow.com/questions/7370801/…
    【解决方案4】:

    这段代码很不准确

    total= 0
    for i in range(1000):
        start= time.clock()
        function()
        end= time.clock()
        total += end-start
    time= total/1000
    

    这段代码不太准确

    start= time.clock()
    for i in range(1000):
        function()
    end= time.clock()
    time= (end-start)/1000
    

    如果函数的运行时间接近时钟的精度,则非常不准确的函数会受到测量偏差的影响。大多数测量的时间只是 0 到几个时钟滴答之间的随机数。

    根据您的系统工作负载,您从单个函数观察到的“时间”可能完全是操作系统调度和其他不可控开销的产物。

    第二个版本(不太准确)的测量偏差较小。如果您的函数真的很快,您可能需要运行 10,000 次以减少操作系统调度和其他开销。

    当然,两者都具有极大的误导性。程序的运行时间——作为一个整体——不是函数运行时间的总和。您只能使用这些数字进行相对比较。它们不是传达太多含义的绝对测量值。

    【讨论】:

    • 为什么是 /1000?方法 time.clock() 将秒作为浮点值返回。如果您希望它返回毫秒,这是有道理的,但是除以 1000 会转换为千秒,这是我以前从未见过的单位。
    • @pixelgrease milli / 1000 = 微,不是公斤 :)
    • @stenci 他建议结果值以秒为单位,例如 1000 秒。如果将其除以 1000,则得到 1 个“千秒”。
    • @pixelgrease, /1000 因为函数执行了 1000 次。所以time是函数执行一次的平均时间。
    • 我建议不要用名为“时间”的变量覆盖模块名称“时间”
    【解决方案5】:

    如果你想为 python 方法计时,即使你测量的块可能会抛出,一个好的方法是使用with 语句。定义一些Timer 类为

    import time
    
    class Timer:    
        def __enter__(self):
            self.start = time.clock()
            return self
    
        def __exit__(self, *args):
            self.end = time.clock()
            self.interval = self.end - self.start
    

    然后您可能想要对可能抛出的连接方法进行计时。使用

    import httplib
    
    with Timer() as t:
        conn = httplib.HTTPConnection('google.com')
        conn.request('GET', '/')
    
    print('Request took %.03f sec.' % t.interval)
    

    __exit()__ 方法即使连接请求失败也会被调用。更准确地说,你可以使用try finally 来查看结果,以防它抛出,就像

    try:
        with Timer() as t:
            conn = httplib.HTTPConnection('google.com')
            conn.request('GET', '/')
    finally:
        print('Request took %.03f sec.' % t.interval)
    

    More details here.

    【讨论】:

      【解决方案6】:

      这样更整洁

      from contextlib import contextmanager
      
      import time
      @contextmanager
      def timeblock(label):
          start = time.clock()
          try:
              yield
          finally:
              end = time.clock()
              print ('{} : {}'.format(label, end - start))
      
      
      
      with timeblock("just a test"):
                  print "yippee"
      

      【讨论】:

      • 不错的答案,简单,到目前为止我发现的唯一一个允许您向计时器功能发送标签的方法。
      • 简洁的解决方案,但有点过于复杂。花了一个小时试图弄清楚为什么 time.sleep(10) 根据这段代码只花了 0.002 秒来执行。 (顺便说一句,time.clock()time.time() 在 python 中的区别很大)
      【解决方案7】:

      类似于@AlexMartelli 的回答

      import timeit
      timeit.timeit(fun, number=10000)
      

      可以解决问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-16
        • 2010-12-13
        • 1970-01-01
        • 2023-01-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多