如何在Python中获取单调时间间隔?

59

我想记录某个操作的实际墙钟时间。目前我的做法如下:

startTime = time.time()
someSQLOrSomething()
print "That took %.3f seconds" % (time.time() - startTime)

但如果在运行SQL查询(或其他操作)时调整了时间,那么这种方法将失败(产生不正确的结果)。

我不只是想对其进行基准测试。我想要在实时应用中记录它,以便在实时系统上查看趋势。

我想要类似于clock_gettime(CLOCK_MONOTONIC,...)的东西,但使用Python。最好不要编写调用clock_gettime()的C模块。


6
我其实不太清楚它被调整的频率。我运行NTP。但是使用单调时钟,我就不必遇到像Oracle RAC错误那样,如果时间被向后设置,系统会重新启动的问题。除了进行小的NTP调整之外,还有可能出现回溯和前进的闰秒。 - Thomas
8
S.Lott认为错误,“闰秒是一个正或负的一秒调整[...]”。这很容易查到,可以在维基百科的“闰秒”文章中的第一句话中找到。因此,当添加一个闰秒时,NTP将向后调整您的系统时间(因为您的系统过快,没有计算到23:59:60),这意味着基于time.time()的测量可能为负数。相信我,许多Oracle服务器由于上述错误在去年新年重启了。我只是举了Oracle作为一个例子,说明有些程序无法处理时间调整。 - Thomas
我不知道为什么(未打补丁的)Oracle 10会这样做。它就是这样,而Oracle(公司)也证实了这一点。 - Thomas
我只是想在这里添加一个我们遇到的用例的评论。在我们的设置中,包括许多vmware系统,我们注意到时间“调整”在客户机vm中经常发生,特别是当主机的负载平均值很高时。这导致像“supervisord”这样依赖于time.time()的东西崩溃,从而使它启动的进程变成孤儿进程。我们通过应用补丁https://github.com/Supervisor/supervisor/pull/468解决了这个问题。 - lonetwin
@Thomas:理论上,可能存在负的闰秒。但实际上,所有的闰秒都是正的。请参阅《闰秒:历史和可能的未来》(The leap second: its history and possible future)。链接:https://www.cl.cam.ac.uk/~mgk25/time/metrologia-leapsecond.pdf。 - jfs
5个回答

82

这个函数足够简单,你可以使用ctypes来访问它:

#!/usr/bin/env python

__all__ = ["monotonic_time"]

import ctypes, os

CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>

class timespec(ctypes.Structure):
    _fields_ = [
        ('tv_sec', ctypes.c_long),
        ('tv_nsec', ctypes.c_long)
    ]

librt = ctypes.CDLL('librt.so.1', use_errno=True)
clock_gettime = librt.clock_gettime
clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]

def monotonic_time():
    t = timespec()
    if clock_gettime(CLOCK_MONOTONIC_RAW , ctypes.pointer(t)) != 0:
        errno_ = ctypes.get_errno()
        raise OSError(errno_, os.strerror(errno_))
    return t.tv_sec + t.tv_nsec * 1e-9

if __name__ == "__main__":
    print monotonic_time()

16
使用CLOCK_MONOTONIC_RAW==4(自Linux 2.6.28以来;仅限于Linux)可以避免NTP调整。 - jfs
为什么要使用 CLOCK_MONOTONIC_RAW 而不是 CLOCK_PROCESS_CPUTIME_ID?它们的区别是什么?(文档链接:http://man7.org/linux/man-pages/man2/clock_gettime.2.html)。在这里可以看到使用 CLOCK_PROCESS_CPUTIME_ID 的 C 示例: https://dev59.com/CGw15IYBdhLWcg3wO5MH - Gabriel Staples
根据我自己的问题(https://www.python.org/dev/peps/pep-0418/#process-time)的答案:`CLOCK_PROCESS_CPUTIME_ID`:“进程时间不能被设置。它不是单调的:当进程处于空闲状态时,时钟会停止。” 它在睡眠或挂起期间计数。基于这项研究和上面的链接,我想使用CLOCK_HIGHRES(也包括挂起时间)或CLOCK_MONOTONIC,在Linux上进行“扭曲”(我认为这意味着在此处进行调整以提高准确性...像校准一样?)。来源:https://www.python.org/dev/peps/pep-0418/#monotonic-clocks。 - Gabriel Staples
我又改变了主意,我会像你一样选择CLOCK_MONOTONIC_RAW,因为它与CLOCK_MONOTONIC类似,但提供对基于硬件的原始时间的访问,该时间不受NTP(网络时间协议)调整或adjtime(3)执行的增量调整的影响。 (http://man7.org/linux/man-pages/man2/clock_gettime.2.html)。所以,我回答了自己的问题,提供了一些有价值的链接来帮助其他人,并完全接受了你选择的时钟,并喜欢那个时钟,用于基本的低级时间戳和精确的间隔测量。 - Gabriel Staples
1
感谢@ArminRonacher的回答,我已将其整合到一个兼容Windows/Linux的模块中,并在此处发布:https://dev59.com/AFoT5IYBdhLWcg3wpg5a#38319607 - Gabriel Staples
显示剩余7条评论

50

在CPython中,我认为它在内部使用CLOCK_MONOTONIC而不是CLOCK_MONOTONIC_RAW,后者甚至在Python 3.3中都不可用。 - Asclepius
@A-B-B: time模块知道CLOCK_MONOTONIC_RAW,尽管我所见到的它并没有使用它。你可以通过ctypes在Python 2.7上定义使用它的时钟 - jfs
2
如果我理解文档正确的话,在Python 3.3中,你可以通过调用time.clock_gettime(time.CLOCK_MONOTONIC_RAW)来获取CLOCK_MONOTONIC_RAW,这非常好,因此当你用它来测量小时间间隔时,当网络通过NTP(网络时间协议)进行时间更新时,你永远不会引入错误。Python时间参考:https://docs.python.org/3/library/time.html#time.CLOCK_MONOTONIC_RAW。 <--注意:仅适用于UNIX / LINUX。对于Windows,只需调用time.clock(),它已经具有微秒或更好的分辨率,因为它使用了QueryPerformanceCounter()。 - Gabriel Staples

9
此问题所指出的,避免Linux上的NTP重新调整需要使用CLOCK_MONOTONIC_RAW。自2.6.28以来,在Linux上将其定义为4。
从Python中可移植地获取C头文件中正确的常量#定义是棘手的;虽然有h2py,但它并不能真正帮助您在运行时获取值。

1
我认为所选答案是不正确的,也没有解释跳变,可以看看我的评论。CLOCK_MONOTONIC和CLOCK_MONOTONIC_RAW都是单调的,它们之间唯一的区别是前者使用NTP校正硬件时钟速度。 - Tobu

4

以下是我如何在 Python 2.7 中获取单调时间的方法:

安装 monotonic 包:

pip install monotonic

然后在Python中:

import monotonic; mtime = monotonic.time.time #now mtime() can be used in place of time.time()
t0 = mtime()
#...do something
elapsed = mtime()-t0 #gives correct elapsed time, even if system clock changed.

编辑:在信任此方法之前,请确保其适用于您的目标操作系统。单调库似乎可以处理某些操作系统中的时钟更改,但不是所有操作系统。


这不是单调的。如果在调用之间更改系统时钟,它将失败,就像调用time.time()一样。在OSX上进行了测试。 - Gabriel
@Gabriel 它在单个 Python 进程中运行;我进行了测试并构建了一个依赖它的机器人。我怀疑如果它对你不起作用,那是因为你重新启动了 Python,这样你就会得到一个新的 Python 进程。 - Luke
只要您不手动更改日期/时间或操作系统不自动更改,它就可以正常工作。 - Gabriel
1
嗯,你说得对;看起来这取决于操作系统。在树莓派上更改系统时间肯定不会影响它,在Raspbian上也是如此,但在Ubuntu 20上似乎会,与库的文档相反。令人失望。 - Luke

2

time.monotonic() 可能很有用:

返回单调时钟的值(以分数秒为单位),即不能向后走的时钟。该时钟不受系统时钟更新的影响。返回值的参考点未定义,因此仅连续调用结果之间的差异是有效的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接