Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 418 – 添加单调时间、性能计数器和进程时间函数

作者:
Cameron Simpson <cs at cskk.id.au>, Jim J. Jewett <jimjjewett at gmail.com>, Stephen J. Turnbull <stephen at xemacs.org>, Victor Stinner <vstinner at python.org>
状态:
最终版
类型:
标准跟踪
创建日期:
2012年3月26日
Python 版本:
3.3

目录

摘要

此 PEP 提议在 Python 3.3 中添加 time.get_clock_info(name)time.monotonic()time.perf_counter()time.process_time() 函数。

基本原理

如果程序使用系统时间来调度事件或实现超时,当系统时间被手动更改或由 NTP 自动调整时,它可能会在错误的时间运行事件或过早或过晚地停止超时。应使用单调时钟来避免受到系统时间更新的影响:time.monotonic()

为了测量函数的性能,可以使用 time.clock(),但在 Windows 和 Unix 上的行为非常不同。在 Windows 上,time.clock() 包括休眠期间经过的时间,而在 Unix 上则不包括。在 Windows 上,time.clock() 的分辨率非常好,但在 Unix 上则很差。新的 time.perf_counter() 函数应该用于始终获取最精确的性能计数器,并具有可移植的行为(例如,包括休眠期间花费的时间)。

到目前为止,Python 没有直接提供可移植函数来测量 CPU 时间。time.clock() 可以在 Unix 上使用,但分辨率很差。resource.getrusage()os.times() 也可以在 Unix 上使用,但它们需要计算内核空间和用户空间中花费的时间总和。新的 time.process_time() 函数作为可移植计数器,始终测量 CPU 时间(不包括休眠期间经过的时间),并具有最佳可用分辨率。

每个操作系统以不同的方式实现时钟和性能计数器,了解具体使用了哪个函数以及时钟的一些属性(如分辨率)非常有用。新的 time.get_clock_info() 函数提供了关于每个 Python 时间函数的所有可用信息。

新函数

  • time.monotonic():超时和调度,不受系统时钟更新影响
  • time.perf_counter():基准测试,短期内最精确的时钟
  • time.process_time():性能分析,进程的 CPU 时间

新函数用户

  • time.monotonic():concurrent.futures、multiprocessing、queue、subprocess、telnet 和 threading 模块用于实现超时
  • time.perf_counter():trace 和 timeit 模块,pybench 程序
  • time.process_time():profile 模块
  • time.get_clock_info():pybench 程序用于显示计时器信息,如分辨率

time.clock() 函数已弃用,因为它不可移植:它在不同的操作系统上的行为不同。应根据您的需求使用 time.perf_counter()time.process_time()time.clock() 已标记为弃用,但未计划移除。

限制

  • 新函数的文档中没有定义系统暂停后时钟的行为。该行为取决于操作系统:请参阅下面的单调时钟部分。一些最新的操作系统提供两种时钟,一种包括系统暂停期间经过的时间,另一种不包括该时间。大多数操作系统只提供一种时钟。
  • time.monotonic() 和 time.perf_counter() 可能会或可能不会被调整。例如,Linux 上的 CLOCK_MONOTONIC 会被倾斜,而 Windows 上的 GetTickCount() 则不会被调整。time.get_clock_info('monotonic')['adjustable'] 可用于检查单调时钟是否可调整。
  • 此 PEP 未提议 time.thread_time() 函数,因为 Python 标准库不需要它,也不是一个常用的功能。此函数仅在 Windows 和 Linux 上可用。在 Linux 上,可以使用 time.clock_gettime(CLOCK_THREAD_CPUTIME_ID)。在 Windows 上,可以使用 ctypes 或其他模块调用 GetThreadTimes() 函数。

Python 函数

新函数

time.get_clock_info(name)

获取指定时钟的信息。支持的时钟名称:

  • "clock": time.clock()
  • "monotonic": time.monotonic()
  • "perf_counter": time.perf_counter()
  • "process_time": time.process_time()
  • "time": time.time()

返回一个 time.clock_info 对象,该对象具有以下属性:

  • implementation (str):底层操作系统函数的名称。示例:"QueryPerformanceCounter()""clock_gettime(CLOCK_REALTIME)"
  • monotonic (bool):如果时钟不能倒退,则为 True。
  • adjustable (bool):如果时钟可以自动(例如,通过 NTP 守护程序)或手动(通过系统管理员)更改,则为 True,否则为 False
  • resolution (float):时钟的秒级分辨率。

time.monotonic()

单调时钟,即不能倒退。它不受系统时钟更新的影响。返回值的参考点是未定义的,因此只有连续调用结果之间的差值有效,并且表示秒数。

在早于 Vista 的 Windows 版本上,time.monotonic() 会检测 GetTickCount() 整数溢出(32 位,49.7 天后翻转)。每次检测到溢出时,它会将内部纪元(参考时间)增加 232。纪元存储在进程局部状态中,因此在运行超过 49 天的两个 Python 进程中,time.monotonic() 的值可能不同。在更新的 Windows 版本和其他操作系统上,time.monotonic() 是系统范围的。

可用性:Windows、Mac OS X、Linux、FreeBSD、OpenBSD、Solaris。在 GNU/Hurd 上不可用。

伪代码[2]

if os.name == 'nt':
    # GetTickCount64() requires Windows Vista, Server 2008 or later
    if hasattr(_time, 'GetTickCount64'):
        def monotonic():
            return _time.GetTickCount64() * 1e-3
    else:
        def monotonic():
            ticks = _time.GetTickCount()
            if ticks < monotonic.last:
                # Integer overflow detected
                monotonic.delta += 2**32
            monotonic.last = ticks
            return (ticks + monotonic.delta) * 1e-3
        monotonic.last = 0
        monotonic.delta = 0

elif sys.platform == 'darwin':
    def monotonic():
        if monotonic.factor is None:
            factor = _time.mach_timebase_info()
            monotonic.factor = timebase[0] / timebase[1] * 1e-9
        return _time.mach_absolute_time() * monotonic.factor
    monotonic.factor = None

elif hasattr(time, "clock_gettime") and hasattr(time, "CLOCK_HIGHRES"):
    def monotonic():
        return time.clock_gettime(time.CLOCK_HIGHRES)

elif hasattr(time, "clock_gettime") and hasattr(time, "CLOCK_MONOTONIC"):
    def monotonic():
        return time.clock_gettime(time.CLOCK_MONOTONIC)

在 Windows 上,不使用 QueryPerformanceCounter(),即使它的分辨率优于 GetTickCount()。它不可靠,问题太多。

time.perf_counter()

具有最高可用分辨率的性能计数器,用于测量短持续时间。它包括休眠期间经过的时间,并且是系统范围的。返回值的参考点是未定义的,因此只有连续调用结果之间的差值有效,并且表示秒数。

它在所有平台上都可用。

伪代码

if os.name == 'nt':
    def _win_perf_counter():
        if _win_perf_counter.frequency is None:
            _win_perf_counter.frequency = _time.QueryPerformanceFrequency()
        return _time.QueryPerformanceCounter() / _win_perf_counter.frequency
    _win_perf_counter.frequency = None

def perf_counter():
    if perf_counter.use_performance_counter:
        try:
            return _win_perf_counter()
        except OSError:
            # QueryPerformanceFrequency() fails if the installed
            # hardware does not support a high-resolution performance
            # counter
            perf_counter.use_performance_counter = False
    if perf_counter.use_monotonic:
        # The monotonic clock is preferred over the system time
        try:
            return time.monotonic()
        except OSError:
            perf_counter.use_monotonic = False
    return time.time()
perf_counter.use_performance_counter = (os.name == 'nt')
perf_counter.use_monotonic = hasattr(time, 'monotonic')

time.process_time()

当前进程的系统和用户 CPU 时间之和。它不包括休眠期间经过的时间。它按定义是进程范围的。返回值的参考点是未定义的,因此只有连续调用结果之间的差值有效。

它在所有平台上都可用。

伪代码[2]

if os.name == 'nt':
    def process_time():
        handle = _time.GetCurrentProcess()
        process_times = _time.GetProcessTimes(handle)
        return (process_times['UserTime'] + process_times['KernelTime']) * 1e-7
else:
    try:
        import resource
    except ImportError:
        has_resource = False
    else:
        has_resource = True

    def process_time():
        if process_time.clock_id is not None:
            try:
                return time.clock_gettime(process_time.clock_id)
            except OSError:
                process_time.clock_id = None
        if process_time.use_getrusage:
            try:
                usage = resource.getrusage(resource.RUSAGE_SELF)
                return usage[0] + usage[1]
            except OSError:
                process_time.use_getrusage = False
        if process_time.use_times:
            try:
                times = _time.times()
                cpu_time = times.tms_utime + times.tms_stime
                return cpu_time / process_time.ticks_per_seconds
            except OSError:
                process_time.use_getrusage = False
        return _time.clock()
    if (hasattr(time, 'clock_gettime')
        and hasattr(time, 'CLOCK_PROF')):
        process_time.clock_id = time.CLOCK_PROF
    elif (hasattr(time, 'clock_gettime')
          and hasattr(time, 'CLOCK_PROCESS_CPUTIME_ID')):
        process_time.clock_id = time.CLOCK_PROCESS_CPUTIME_ID
    else:
        process_time.clock_id = None
    process_time.use_getrusage = has_resource
    process_time.use_times = hasattr(_time, 'times')
    if process_time.use_times:
        # sysconf("SC_CLK_TCK"), or the HZ constant, or 60
        process_time.ticks_per_seconds = _times.ticks_per_seconds

现有函数

time.time()

系统时间通常是民用时间。它按定义是系统范围的。它可以由系统管理员手动设置,或由 NTP 守护程序自动设置。

它在所有平台上都可用,并且不会失败。

伪代码[2]

if os.name == "nt":
    def time():
        return _time.GetSystemTimeAsFileTime()
else:
    def time():
        if hasattr(time, "clock_gettime"):
            try:
                return time.clock_gettime(time.CLOCK_REALTIME)
            except OSError:
                # CLOCK_REALTIME is not supported (unlikely)
                pass
        if hasattr(_time, "gettimeofday"):
            try:
                return _time.gettimeofday()
            except OSError:
                # gettimeofday() should not fail
                pass
        if hasattr(_time, "ftime"):
            return _time.ftime()
        else:
            return _time.time()

time.sleep()

暂停执行给定的秒数。实际的暂停时间可能小于请求的时间,因为任何捕获到的信号都会在执行该信号的捕获例程后终止 time.sleep()。此外,由于系统中其他活动的调度,暂停时间可能比请求的时间任意延长。

伪代码[2]

try:
    import select
except ImportError:
    has_select = False
else:
    has_select = hasattr(select, "select")

if has_select:
    def sleep(seconds):
        return select.select([], [], [], seconds)

elif hasattr(_time, "delay"):
    def sleep(seconds):
        milliseconds = int(seconds * 1000)
        _time.delay(milliseconds)

elif os.name == "nt":
    def sleep(seconds):
        milliseconds = int(seconds * 1000)
        win32api.ResetEvent(hInterruptEvent);
        win32api.WaitForSingleObject(sleep.sigint_event, milliseconds)

    sleep.sigint_event = win32api.CreateEvent(NULL, TRUE, FALSE, FALSE)
    # SetEvent(sleep.sigint_event) will be called by the signal handler of SIGINT

elif os.name == "os2":
    def sleep(seconds):
        milliseconds = int(seconds * 1000)
        DosSleep(milliseconds)

else:
    def sleep(seconds):
        seconds = int(seconds)
        _time.sleep(seconds)

已弃用函数

time.clock()

在 Unix 上,返回当前处理器时间作为以秒表示的浮点数。它按定义是进程范围的。分辨率,以及“处理器时间”含义的精确定义,取决于同名 C 函数的定义,但在任何情况下,这都是用于基准测试 Python 或计时算法的函数。

在 Windows 上,此函数返回自首次调用此函数以来经过的挂钟秒数,作为浮点数,基于 Win32 函数 QueryPerformanceCounter()。分辨率通常优于一微秒。它是系统范围的。

伪代码[2]

if os.name == 'nt':
    def clock():
        try:
            return _win_perf_counter()
        except OSError:
            # QueryPerformanceFrequency() fails if the installed
            # hardware does not support a high-resolution performance
            # counter
            pass
        return _time.clock()
else:
    clock = _time.clock

替代方案:API 设计

time.monotonic() 的其他名称

  • time.counter()
  • time.metronomic()
  • time.seconds()
  • time.steady():“稳定”是模糊的:它对不同的人意味着不同的东西。例如,在 Linux 上,CLOCK_MONOTONIC 会被调整。如果我们将实时作为参考时钟,我们可以说 CLOCK_MONOTONIC 是稳定的。但是 CLOCK_MONOTONIC 在系统暂停时会暂停,而实时包括任何在暂停期间花费的时间。
  • time.timeout_clock()
  • time.wallclock():time.monotonic() 不是系统时间,也就是“挂钟时间”,而是一个起点未指定的单调时钟。

“time.try_monotonic()”这个名称也曾被提议用于 time.monotonic() 的一个旧版本,当没有单调时钟可用时,它会回退到系统时间。

time.perf_counter() 的其他名称

  • time.high_precision()
  • time.highres()
  • time.hires()
  • time.performance_counter()
  • time.timer()

仅公开操作系统时钟

为了不必定义高层次时钟(这是一项艰巨的任务),一种更简单的方法是只公开操作系统时钟。例如,time.clock_gettime() 和相关的时钟标识符已添加到 Python 3.3 中。

time.monotonic():回退到系统时间

如果没有单调时钟可用,time.monotonic() 会回退到系统时间。

问题

  • 在文档中正确定义这样的函数很困难:它是否单调?它是否稳定?它是否被调整?
  • 一些用户希望在没有单调时钟可用时决定如何操作:使用另一个时钟、显示错误或执行其他操作。

提出了不同的 API 来定义此类函数。

一个带有标志的函数:time.monotonic(fallback=True)

  • time.monotonic(fallback=True) 在没有单调时钟可用或单调时钟失败时回退到系统时间。
  • 如果单调时钟失败,time.monotonic(fallback=False) 会引发 OSError;如果系统不提供单调时钟,则会引发 NotImplementedError

作为调用者中的常量传递的关键字参数通常是糟糕的 API。

为函数引发 NotImplementedError 在 Python 中并不常见,应避免。

一个 time.monotonic() 函数,无标志

time.monotonic() 返回 (time: float, is_monotonic: bool)。

另一种方法是使用函数属性:time.monotonic.is_monotonic。在首次调用 time.monotonic() 之前,属性值将为 None。

从约束列表中选择时钟

所提议的 PEP 提供了一些新的时钟,但其保证故意宽松,以便在不同平台上提供有用的时钟。这固有地将策略嵌入到调用中,因此调用者必须选择一个策略。

“选择时钟”方法建议提供一个额外的 API,允许调用者在必要时通过使大多数平台时钟可用并让调用者从中选择来实施自己的策略。PEP 建议的时钟仍有望用于常见的简单用例。

为此,需要两个设施:时钟枚举和时钟元数据,以使用户能够评估其适用性。

主要接口是一个函数,使简单的选择变得容易:调用者可以使用 time.get_clock(*flags) 和一些标志组合。这至少包括:

  • time.MONOTONIC:时钟不能倒退
  • time.STEADY:时钟速率稳定
  • time.ADJUSTED:时钟可能被调整,例如通过 NTP
  • time.HIGHRES:分辨率最高的时钟

它返回一个时钟对象,该对象具有一个 .now() 方法,返回当前时间。时钟对象带有描述时钟功能集的元数据;其 .flags 字段将至少包含所有请求的标志。

如果找不到匹配的时钟,time.get_clock() 返回 None,因此可以使用 or 运算符链式调用。简单策略决策的示例:

T = get_clock(MONOTONIC) or get_clock(STEADY) or get_clock()
t = T.now()

可用时钟总是至少包含 time.time() 的包装器,因此最后一次不带标志的调用总是可以用于获取一个可用的时钟。

系统时钟标志示例

  • QueryPerformanceCounter: MONOTONIC | HIGHRES
  • GetTickCount: MONOTONIC | STEADY
  • CLOCK_MONOTONIC: MONOTONIC | STEADY (或 Linux 上仅 MONOTONIC)
  • CLOCK_MONOTONIC_RAW: MONOTONIC | STEADY
  • gettimeofday(): (无标志)

时钟对象包含其他元数据,包括时钟标志(带有上述额外功能标志)、底层操作系统设施的名称和时钟精度。

time.get_clock() 仍然选择单个时钟;还需要一个枚举设施。最明显的方法是提供与 time.get_clock() 具有相同签名的 time.get_clocks(),但返回与请求标志匹配的所有时钟的序列。因此,不请求标志将枚举所有可用时钟,允许调用者根据其元数据在它们之间进行任意选择。

部分实现示例:clockutils.py

解决操作系统错误?

Python 是否应该通过计算时钟值和先前值的最大值来确保单调时钟真正单调?

由于使用静态变量缓存上次返回的值相对简单,因此使用它来确保返回的值确实是单调的可能很有趣。

  • 虚拟机提供的时钟可靠性较低。
  • QueryPerformanceCounter() 存在已知错误(只有一个尚未修复)

Python 可能只解决一个特定的已知操作系统错误:KB274323 包含一个解决该错误的代码示例(使用 GetTickCount() 检测 QueryPerformanceCounter() 跳跃)。

“纠正”非单调性问题

  • 如果时钟意外地向前设置了一小时,然后又调回,你将在一小时内没有一个有用的时钟
  • 缓存不跨进程共享,因此不同的进程看不到相同的时钟值

词汇表

准确性:
给定仪器测量值与真实值之间的偏差量。另请参阅准确度和精度。时钟的不准确性可能由精度不足、漂移或时钟初始设置不正确引起(例如,线程计时本质上不准确,因为完美同步重置计数器相当困难)。
已调整:
将时钟重置为正确的时间。这可以通过<阶跃>或<倾斜>来完成。
民用时间:
一天中的时间;系统外部的时间。上午 10:45:13 是民用时间;45 秒不是。由现有函数 time.localtime()time.gmtime() 提供。此 PEP 未更改。
时钟:
测量时间的仪器。不同的时钟具有不同的特性;例如,具有纳秒<精度>的时钟可能在几分钟后开始<漂移>,而精度较低的时钟可以在几天内保持准确。本 PEP 主要关注使用秒作为单位的时钟。
计数器:
每次特定事件发生时递增的时钟。计数器是严格单调的,但不是单调时钟。它可用于生成唯一(且有序)的时间戳,但这些时间戳无法映射到<民用时间>;嘀嗒声的创建可能具有突发性,同一毫秒内多次前进,然后几天内没有任何前进。
CPU 时间:
衡量在某个任务上花费了多少 CPU 努力。CPU 秒通常被标准化(因此在同一实际秒内可能出现可变数字)。CPU 秒在性能分析时可能很重要,但它们不直接映射到用户响应时间,也不能直接与(实时)秒进行比较。
漂移:
与“真实”时间(系统外部定义)相比的累积误差。漂移可能由不精确或时钟时间前进的平均速率与真实时间的平均速率之间的差异引起。
纪元:
时钟的参考点。对于提供<民用时间>的时钟,这通常是 1970 年 1 月 1 日午夜。对于<单调时钟>,纪元可能未定义(表示为 None)。
延迟:
延迟。当一个时钟调用返回时,<实时>已经前进,可能超过时钟的精度。
单调:
单调时钟在实践中应具备的特性。最多只朝一个方向移动;对于时钟,这个方向是向前。时钟也应<稳定>,并且应可转换为秒为单位。权衡通常包括缺乏定义的<纪元>或与<民用时间>的映射。
精度:
同一仪器对相同物理值测量之间的偏差量。时钟的不精确性可能由时钟时间相对于实时前进速率的波动引起,包括通过倾斜调整时钟。
进程时间:
自进程开始以来经过的时间。它通常以而不是<实时>测量,并且在进程暂停时通常不会前进。
实时:
真实世界中的时间。这与<民用时间>不同之处在于它未被<调整>,但它们应该以同步方式前进。它与“实时[操作系统]”中的“实时”无关。有时被称为“挂钟时间”以避免歧义;不幸的是,这又引入了不同的歧义。
决议:
两个物理值之间导致给定仪器测量结果不同的最小差异。
倾斜:
对时钟速度进行轻微更改,通常旨在纠正相对于外部权威的<漂移>。
稳定性:
准确性的持久性。<漂移>的预期量度。
稳定:
具有高<稳定性>和相对高<准确性>和<精度>的时钟。实际上,它通常用于指示<单调时钟>,但更强调后续滴答之间持续时间的一致性。
步进:
表示时间的瞬时变化。不是加速或减慢时钟(<倾斜>),而是永久添加一个偏移量。
系统时间:
操作系统表示的时间。
线程时间:
自线程开始以来经过的时间。它通常以而不是<实时>测量,并且在线程空闲时通常不会前进。
挂钟:
墙上时钟显示的时间。这通常用作<实时>的同义词;不幸的是,挂钟时间本身是模棱两可的。

硬件时钟

硬件时钟列表

  • HPET:高精度事件计时器 (HPET) 芯片由一个 64 位向上计数器(主计数器)组成,计数频率至少为 10 MHz,以及一套多达 256 个比较器(至少 3 个)。每个 HPET 最多可以有 32 个计时器。HPET 每天可能导致约 3 秒的漂移。
  • TSC (时间戳计数器):历史上,TSC 随着每个内部处理器时钟周期而增加,但现在速率通常是恒定的(即使处理器改变频率),并且通常等于最大处理器频率。多个核心具有不同的 TSC 值。系统休眠将重置 TSC 值。RDTSC 指令可用于读取此计数器。CPU 频率缩放用于节能。
  • ACPI 电源管理计时器:ACPI 24 位计时器,频率为 3.5 MHz (3,579,545 Hz)。
  • Cyclone:Cyclone 计时器在 IBM Extended X-Architecture (EXA) 芯片组(包括使用 IBM “Summit”系列芯片组的计算机,例如 x440)上使用 32 位计数器。这在 IA32 和 IA64 架构中可用。
  • PIT(可编程中断计时器):Intel 8253/8254 芯片组,可配置频率范围为 18.2 Hz - 1.2 MHz。它使用 16 位计数器。
  • RTC(实时时钟)。大多数 RTC 使用频率为 32,768 Hz 的晶体振荡器。

Linux 时钟源

Linux 内核中时间有 4 种实现:UTIME (1996)、定时器轮 (1997)、HRT (2001) 和 hrtimers (2007)。后者是 George Anzinger 于 2001 年启动的“高分辨率定时器”项目的结果,Thomas Gleixner 和 Douglas Niehaus 贡献了力量。hrtimers 实现在 2007 年发布的 Linux 2.6.21 中合并。

hrtimers 支持各种时钟源。它为每个源设置一个优先级来决定使用哪一个。Linux 支持以下时钟源:

  • tsc
  • hpet
  • pit
  • pmtmr:ACPI 电源管理计时器
  • cyclone

高分辨率定时器并非在所有硬件架构上都受支持。它们至少在 x86/x86_64、ARM 和 PowerPC 上提供。

clock_getres() 对于 CLOCK_REALTIMECLOCK_MONOTONIC 返回 1 纳秒,无论底层时钟源如何。请阅读 Thomas Gleixner(2012 年 2 月 9 日)的Re: clock_getres() and real resolution 以获取解释。

/sys/devices/system/clocksource/clocksource0 目录包含两个有用的文件:

  • available_clocksource:可用时钟源列表
  • current_clocksource:当前使用的时钟源。可以通过将时钟源的名称写入此文件来更改当前时钟源。

/proc/timer_list 包含所有硬件计时器的列表。

另请阅读 time(7) 手册页:“时间与计时器概述”。

FreeBSD 时间计数器

kern.timecounter.choice 列出可用硬件时钟及其优先级。sysctl 程序可用于更改时间计数器。示例:

# dmesg | grep Timecounter
Timecounter "i8254" frequency 1193182 Hz quality 0
Timecounter "ACPI-safe" frequency 3579545 Hz quality 850
Timecounter "HPET" frequency 100000000 Hz quality 900
Timecounter "TSC" frequency 3411154800 Hz quality 800
Timecounters tick every 10.000 msec
# sysctl kern.timecounter.choice
kern.timecounter.choice: TSC(800) HPET(900) ACPI-safe(850) i8254(0) dummy(-1000000)
# sysctl kern.timecounter.hardware="ACPI-fast"
kern.timecounter.hardware: HPET -> ACPI-fast

可用时钟

  • “TSC”:处理器的时戳计数器
  • “HPET”:高精度事件计时器
  • “ACPI-fast”:ACPI 电源管理计时器(快速模式)
  • “ACPI-safe”:ACPI 电源管理计时器(安全模式)
  • “i8254”:带 Intel 8254 芯片组的 PIT

提交 222222(2011 年 5 月)将 ACPI-fast 时间计数器质量降低到 900,将 HPET 时间计数器质量提高到 950:“现代平台上的 HPET 通常比 ACPI 计时器具有更好的分辨率和更低的延迟。”

阅读 Poul-Henning Kamp(2002 年)为 FreeBSD 项目撰写的时间计数器:SMP 内核中高效精确的计时

性能

读取硬件时钟会产生开销。下表比较了 Linux 3.3(搭载 Intel Core i7-2600 @ 3.40GHz,8 核)上不同硬件时钟的性能。使用 bench_time.c 程序填充这些表格。

函数 TSC ACPI PM HPET
time() 2 ns 2 ns 2 ns
CLOCK_REALTIME_COARSE 10 ns 10 ns 10 ns
CLOCK_MONOTONIC_COARSE 12 ns 13 ns 12 ns
CLOCK_THREAD_CPUTIME_ID 134 ns 135 ns 135 ns
CLOCK_PROCESS_CPUTIME_ID 127 ns 129 ns 129 ns
clock() 146 ns 146 ns 143 ns
gettimeofday() 23 ns 726 ns 637 ns
CLOCK_MONOTONIC_RAW 31 ns 716 ns 607 ns
CLOCK_REALTIME 27 ns 707 ns 629 ns
CLOCK_MONOTONIC 27 ns 723 ns 635 ns

FreeBSD 8.0 在带有硬件虚拟化的 kvm 中

函数 TSC ACPI-Safe HPET i8254
time() 191 纳秒 188 纳秒 189 纳秒 188 纳秒
CLOCK_SECOND 187 纳秒 184 纳秒 187 纳秒 183 纳秒
CLOCK_REALTIME_FAST 189 纳秒 180 纳秒 187 纳秒 190 纳秒
CLOCK_UPTIME_FAST 191 纳秒 185 纳秒 186 纳秒 196 纳秒
CLOCK_MONOTONIC_FAST 188 纳秒 187 纳秒 188 纳秒 189 纳秒
CLOCK_THREAD_CPUTIME_ID 208 纳秒 206 纳秒 207 纳秒 220 纳秒
CLOCK_VIRTUAL 280 纳秒 279 纳秒 283 纳秒 296 纳秒
CLOCK_PROF 289 纳秒 280 纳秒 282 纳秒 286 纳秒
clock() 342 纳秒 340 纳秒 337 纳秒 344 纳秒
CLOCK_UPTIME_PRECISE 197 纳秒 10380 纳秒 4402 纳秒 4097 纳秒
CLOCK_REALTIME 196 纳秒 10376 纳秒 4337 纳秒 4054 纳秒
CLOCK_MONOTONIC_PRECISE 198 纳秒 10493 纳秒 4413 纳秒 3958 纳秒
CLOCK_UPTIME 197 纳秒 10523 纳秒 4458 纳秒 4058 纳秒
gettimeofday() 202 纳秒 10524 纳秒 4186 纳秒 3962 纳秒
CLOCK_REALTIME_PRECISE 197 纳秒 10599 纳秒 4394 纳秒 4060 纳秒
CLOCK_MONOTONIC 201 纳秒 10766 纳秒 4498 纳秒 3943 纳秒

每个函数被调用 100,000 次,并使用 CLOCK_MONOTONIC 获取之前和之后的时间。基准测试运行 5 次,保留最短时间。

NTP 调整

NTP 有不同的方法来调整时钟

  • “倾斜”:将时钟频率更改为稍微快或慢(通过 adjtime() 完成)。由于倾斜速率限制为每秒 0.5 毫秒,因此每秒的调整需要 2000 秒的摊销间隔。因此,几秒钟的调整可能需要数小时或数天才能摊销。
  • “步进”:在单个离散步骤中大幅跳跃(通过 settimeofday() 完成)

默认情况下,如果偏移量小于 128 毫秒,则时间会倾斜,否则会步进。

如果希望测量“真实”时间(而不是像 CPU 周期那样的时间对象),通常希望倾斜(即,我们应该使用 CLOCK_MONOTONIC,而不是 CLOCK_MONOTONIC_RAW)。这是因为来自您的 NTP 连接另一端的时钟可能更善于计时:毕竟,那三万五千美元的铯原子钟计时功能有望比您 PC 的 3 美元石英晶体更好。

NTP 守护程序文档中获取更多详细信息。

操作系统时间函数

单调时钟

名称 C 分辨率 已调整 包含休眠 包含暂停
gethrtime() 1 纳秒
CLOCK_HIGHRES 1 纳秒
CLOCK_MONOTONIC 1 纳秒 在 Linux 上倾斜
CLOCK_MONOTONIC_COARSE 1 纳秒 在 Linux 上倾斜
CLOCK_MONOTONIC_RAW 1 纳秒
CLOCK_BOOTTIME 1 纳秒 ?
CLOCK_UPTIME 1 纳秒 ?
mach_absolute_time() 1 纳秒
QueryPerformanceCounter() - ?
GetTickCount[64]() 1 毫秒
timeGetTime() 1 毫秒 ?

“C 分辨率”列是底层 C 结构的分辨率。

x86_64 上的时钟分辨率示例

名称 操作系统 OS 分辨率 Python 分辨率
QueryPerformanceCounter Windows 7 10 ns 10 ns
CLOCK_HIGHRES SunOS 5.11 2 ns 265 纳秒
CLOCK_MONOTONIC Linux 3.0 1 纳秒 322 纳秒
CLOCK_MONOTONIC_RAW Linux 3.3 1 纳秒 628 纳秒
CLOCK_BOOTTIME Linux 3.3 1 纳秒 628 纳秒
mach_absolute_time() Mac OS 10.6 1 纳秒 3 微秒
CLOCK_MONOTONIC FreeBSD 8.2 11 纳秒 5 微秒
CLOCK_MONOTONIC OpenBSD 5.0 10 毫秒 5 微秒
CLOCK_UPTIME FreeBSD 8.2 11 纳秒 6 微秒
CLOCK_MONOTONIC_COARSE Linux 3.3 1 毫秒 1 毫秒
CLOCK_MONOTONIC_COARSE Linux 3.0 4 毫秒 4 毫秒
GetTickCount64() Windows 7 16 毫秒 15 毫秒

“OS 分辨率”是操作系统宣布的分辨率。“Python 分辨率”是使用 clock_resolution.py 程序在 Python 中计算的两次调用时间函数之间的最小差异。

mach_absolute_time

Mac OS X 提供了一个单调时钟:mach_absolute_time()。它基于自系统启动以来的绝对经过时间。它未被调整,也无法设置。

mach_timebase_info() 提供了一个分数,用于将时钟值转换为纳秒数。另请参阅 Technical Q&A QA1398

mach_absolute_time() 在 PowerPC CPU 上休眠时停止,但在 Intel CPU 上不停止:mach_absolute_time() 在 i386/ppc 上的不同行为

CLOCK_MONOTONIC, CLOCK_MONOTONIC_RAW, CLOCK_BOOTTIME

CLOCK_MONOTONIC 和 CLOCK_MONOTONIC_RAW 表示自某个未指定起点以来的单调时间。它们无法设置。可以使用 clock_getres() 读取分辨率。

文档:请参阅您操作系统的手册页。示例:

CLOCK_MONOTONIC 至少在以下操作系统上可用:

  • DragonFly BSD, FreeBSD >= 5.0, OpenBSD, NetBSD
  • Linux
  • Solaris

以下操作系统不支持 CLOCK_MONOTONIC:

在 Linux 上,NTP 可能会调整 CLOCK_MONOTONIC 速率(倾斜),但它不能向后跳跃。

CLOCK_MONOTONIC_RAW 特定于 Linux。它类似于 CLOCK_MONOTONIC,但提供对不受 NTP 调整的原始硬件时间访问。CLOCK_MONOTONIC_RAW 需要 Linux 2.6.28 或更高版本。

Linux 2.6.39 和 glibc 2.14 引入了一个新时钟:CLOCK_BOOTTIME。CLOCK_BOOTTIME 与 CLOCK_MONOTONIC 相同,只是它还包括在暂停期间花费的任何时间。另请阅读 Waking systems from suspend(2011 年 3 月)。

机器暂停时 CLOCK_MONOTONIC 停止。

自 Linux 2.6.32 起,Linux 还提供了 CLOCK_MONOTONIC_COARSE。它类似于 CLOCK_MONOTONIC,精度较低但速度更快。

如果系统不支持指定的时钟,即使标准 C 库支持,clock_gettime() 也会失败。例如,CLOCK_MONOTONIC_RAW 需要内核版本 2.6.28 或更高版本。

Windows: QueryPerformanceCounter

高分辨率性能计数器。它是单调的。计数器的频率可以使用 QueryPerformanceFrequency() 读取。分辨率是 1 / QueryPerformanceFrequency()。

它具有更高的分辨率,但长期精度低于 GetTickCount() 和 timeGetTime() 时钟。例如,与低精度时钟相比,它会漂移。

文档

QueryPerformanceCounter 使用的硬件时钟

  • Windows XP:Intel 处理器上的 RDTSC 指令,时钟频率是处理器频率(200 MHz 到 3 GHz 之间,现在通常大于 1 GHz)。
  • Windows 2000:ACPI 电源管理计时器,频率 = 3,549,545 Hz。可以通过 boot.ini 中的“/usepmtimer”标志强制使用。

QueryPerformanceFrequency() 只应调用一次:系统运行时频率不会改变。如果安装的硬件不支持高分辨率性能计数器,它将失败。

QueryPerformanceCounter() 无法调整:SetSystemTimeAdjustment() 只调整系统时间。

错误

  • 由于硬件错误,性能计数器值可能会意外地向前跳跃,请参阅 KB274323
  • 在 VirtualBox 上,QueryPerformanceCounter() 在低位溢出时不会每次都增加高位,请参阅 单调计时器 (2009)。
  • VirtualBox 的 HPET 虚拟设备有一个错误:QueryPerformanceCounter() 大约跳跃了 42 秒(问题 #8707)。
  • Windows XP 存在一个错误(参见 KB896256):在多处理器计算机上,QueryPerformanceCounter() 为每个处理器返回不同的值。该错误已在 Windows XP SP2 中修复。
  • 变频处理器问题:频率根据工作负载改变以降低功耗。
  • Chromium 不在 Athlon X2 CPU(型号 15)上使用 QueryPerformanceCounter(),因为“QueryPerformanceCounter 不可靠”(参见 Chromium 源代码中的 base/time_win.cc)

Windows: GetTickCount(), GetTickCount64()

GetTickCount() 和 GetTickCount64() 是单调的,不会失败,并且不受 SetSystemTimeAdjustment() 调整。MSDN 文档:GetTickCount()GetTickCount64()。分辨率可以使用 GetSystemTimeAdjustment() 读取。

GetTickCount() 或 GetTickCount64() 检索的经过时间包括系统在睡眠或休眠中花费的时间。

GetTickCount64() 已添加到 Windows Vista 和 Windows Server 2008。

可以使用未文档化的 NtSetTimerResolution() 函数来提高精度。有一些应用程序使用此未文档化函数,例如:计时器分辨率

WaitForSingleObject() 使用与 GetTickCount() 相同的计时器,具有相同的精度。

Windows: timeGetTime

timeGetTime 函数以毫秒为单位检索系统时间。系统时间是自 Windows 启动以来经过的时间。阅读 timeGetTime() 文档

timeGetTime() 的返回类型是一个 32 位无符号整数。与 GetTickCount() 一样,timeGetTime() 在 2^32 毫秒(49.7 天)后溢出。

timeGetTime() 检索到的经过时间包括系统在睡眠中花费的时间。

timeGetTime 函数的默认精度可以是五毫秒或更高,具体取决于机器。

timeBeginPeriod() 可用于将 timeGetTime() 的精度提高到 1 毫秒,但这会负面影响功耗。调用 timeBeginPeriod() 还会影响其他一些计时调用的粒度,例如 CreateWaitableTimer()、WaitForSingleObject() 和 Sleep()。

注意

timeGetTime() 和 timeBeginPeriod() 是 Windows 多媒体库的一部分,因此需要将程序链接到 winmm 或动态加载库。

Solaris: CLOCK_HIGHRES

Solaris 操作系统有一个 CLOCK_HIGHRES 计时器,它尝试使用最佳硬件源,并且可能提供接近纳秒的分辨率。CLOCK_HIGHRES 是不可调的、高分辨率时钟。对于使用 CLOCK_HIGHRES 的 clockid_t 值创建的计时器,系统将尝试使用最佳硬件源。

CLOCK_HIGHRES 的分辨率可以使用 clock_getres() 读取。

Solaris: gethrtime

gethrtime() 函数返回当前高分辨率实时。时间表示为自过去某个任意时间以来的纳秒数;它与一天中的时间没有任何关联,因此不受 adjtime() 或 settimeofday() 的重置或漂移影响。高分辨率计时器非常适合性能测量任务,需要廉价、准确的间隔计时。

gethrtime() 的线性度在挂起-恢复周期中不保留(Bug 4272663)。

阅读 Solaris 11 的 gethrtime() 手册页

在 Solaris 上,gethrtime() 与 clock_gettime(CLOCK_MONOTONIC) 相同。

系统时间

名称 C 分辨率 包含休眠 包含暂停
CLOCK_REALTIME 1 纳秒
CLOCK_REALTIME_COARSE 1 纳秒
GetSystemTimeAsFileTime 100 ns
gettimeofday() 1 微秒
ftime() 1 毫秒
time() 1 秒

“C 分辨率”列是底层 C 结构的分辨率。

x86_64 上的时钟分辨率示例

名称 操作系统 OS 分辨率 Python 分辨率
CLOCK_REALTIME SunOS 5.11 10 毫秒 238 纳秒
CLOCK_REALTIME Linux 3.0 1 纳秒 238 纳秒
gettimeofday() Mac OS 10.6 1 微秒 4 微秒
CLOCK_REALTIME FreeBSD 8.2 11 纳秒 6 微秒
CLOCK_REALTIME OpenBSD 5.0 10 毫秒 5 微秒
CLOCK_REALTIME_COARSE Linux 3.3 1 毫秒 1 毫秒
CLOCK_REALTIME_COARSE Linux 3.0 4 毫秒 4 毫秒
GetSystemTimeAsFileTime() Windows 7 16 毫秒 1 毫秒
ftime() Windows 7 - 1 毫秒

“OS 分辨率”是操作系统宣布的分辨率。“Python 分辨率”是使用 clock_resolution.py 程序在 Python 中计算的两次调用时间函数之间的最小差异。

Windows: GetSystemTimeAsFileTime

系统时间可以使用 GetSystemTimeAsFileTime()、ftime() 和 time() 读取。系统时间的分辨率可以使用 GetSystemTimeAdjustment() 读取。

阅读 GetSystemTimeAsFileTime() 文档

系统时间可以使用 SetSystemTime() 设置。

UNIX 上的系统时间

gettimeofday()、ftime()、time() 和 clock_gettime(CLOCK_REALTIME) 返回系统时间。CLOCK_REALTIME 的分辨率可以使用 clock_getres() 读取。

系统时间可以使用 settimeofday() 或 clock_settime(CLOCK_REALTIME) 设置。

自 Linux 2.6.32 起,Linux 还提供了 CLOCK_REALTIME_COARSE。它类似于 CLOCK_REALTIME,精度较低但速度更快。

Alexander Shishkin 为 Linux 提出了一个 API,用于在系统时钟更改时收到通知:timerfd: 添加 TFD_NOTIFY_CLOCK_SET 以监视时钟更改(API 的第 4 版,2011 年 3 月)。该 API 尚未被接受,但 CLOCK_BOOTTIME 提供了类似的功能。

进程时间

进程时间无法设置。它不是单调的:当进程空闲时,时钟停止。

名称 C 分辨率 包含休眠 包含暂停
GetProcessTimes() 100 ns
CLOCK_PROCESS_CPUTIME_ID 1 纳秒
getrusage(RUSAGE_SELF) 1 微秒
times() -
clock() - Windows 上是,否则否

“C 分辨率”列是底层 C 结构的分辨率。

x86_64 上的时钟分辨率示例

名称 操作系统 OS 分辨率 Python 分辨率
CLOCK_PROCESS_CPUTIME_ID Linux 3.3 1 纳秒 1 纳秒
CLOCK_PROF FreeBSD 8.2 10 毫秒 1 微秒
getrusage(RUSAGE_SELF) FreeBSD 8.2 - 1 微秒
getrusage(RUSAGE_SELF) SunOS 5.11 - 1 微秒
CLOCK_PROCESS_CPUTIME_ID Linux 3.0 1 纳秒 1 微秒
getrusage(RUSAGE_SELF) Mac OS 10.6 - 5 微秒
clock() Mac OS 10.6 1 微秒 5 微秒
CLOCK_PROF OpenBSD 5.0 - 5 微秒
getrusage(RUSAGE_SELF) Linux 3.0 - 4 毫秒
getrusage(RUSAGE_SELF) OpenBSD 5.0 - 8 毫秒
clock() FreeBSD 8.2 8 毫秒 8 毫秒
clock() Linux 3.0 1 微秒 10 毫秒
times() Linux 3.0 10 毫秒 10 毫秒
clock() OpenBSD 5.0 10 毫秒 10 毫秒
times() OpenBSD 5.0 10 毫秒 10 毫秒
times() Mac OS 10.6 10 毫秒 10 毫秒
clock() SunOS 5.11 1 微秒 10 毫秒
times() SunOS 5.11 1 微秒 10 毫秒
GetProcessTimes() Windows 7 16 毫秒 16 毫秒
clock() Windows 7 1 毫秒 1 毫秒

“OS 分辨率”是操作系统宣布的分辨率。“Python 分辨率”是使用 clock_resolution.py 程序在 Python 中计算的两次调用时间函数之间的最小差异。

函数

  • Windows: GetProcessTimes()。分辨率可以使用 GetSystemTimeAdjustment() 读取。
  • clock_gettime(CLOCK_PROCESS_CPUTIME_ID):来自 CPU 的高分辨率每进程计时器。分辨率可以使用 clock_getres() 读取。
  • clock()。分辨率为 1 / CLOCKS_PER_SEC。
    • Windows:自进程启动以来经过的挂钟时间(以秒为单位的经过时间乘以 CLOCKS_PER_SEC)。包括休眠期间经过的时间。它可能会失败。
    • UNIX:返回程序使用的处理器时间的近似值。
  • getrusage(RUSAGE_SELF) 返回当前进程资源使用情况的结构。ru_utime 是用户 CPU 时间,ru_stime 是系统 CPU 时间。
  • times():进程时间结构。分辨率为 1 / ticks_per_seconds,其中 ticks_per_seconds 是 sysconf(_SC_CLK_TCK) 或 HZ 常量。

Python 源代码包含一个可移植库,用于获取进程时间(CPU 时间):Tools/pybench/systimes.py

另请参阅 QueryProcessCycleTime() 函数(所有线程的周期时间之和)和 clock_getcpuclockid()

线程时间

线程时间无法设置。它不是单调的:当线程空闲时,时钟停止。

名称 C 分辨率 包含休眠 包含暂停
CLOCK_THREAD_CPUTIME_ID 1 纳秒 纪元变化
GetThreadTimes() 100 ns ?

“C 分辨率”列是底层 C 结构的分辨率。

x86_64 上的时钟分辨率示例

名称 操作系统 OS 分辨率 Python 分辨率
CLOCK_THREAD_CPUTIME_ID FreeBSD 8.2 1 微秒 1 微秒
CLOCK_THREAD_CPUTIME_ID Linux 3.3 1 纳秒 649 纳秒
GetThreadTimes() Windows 7 16 毫秒 16 毫秒

“OS 分辨率”是操作系统宣布的分辨率。“Python 分辨率”是使用 clock_resolution.py 程序在 Python 中计算的两次调用时间函数之间的最小差异。

函数

  • Windows: GetThreadTimes()。分辨率可以使用 GetSystemTimeAdjustment() 读取。
  • clock_gettime(CLOCK_THREAD_CPUTIME_ID):线程专用 CPU 时间时钟。它使用 CPU 周期数,而不是秒数。分辨率可以使用 clock_getres() 读取。

另请参阅 QueryThreadCycleTime() 函数(指定线程的周期时间)和 pthread_getcpuclockid()。

Windows: QueryUnbiasedInterruptTime

从有偏差的中断时间量和当前休眠偏差量获取当前无偏差的中断时间。此时间不受电源管理休眠转换的影响。

QueryUnbiasedInterruptTime 函数检索到的经过时间仅包括系统在工作状态下花费的时间。QueryUnbiasedInterruptTime() 不是单调的。

QueryUnbiasedInterruptTime() 在 Windows 7 中引入。

另请参阅 QueryIdleProcessorCycleTime() 函数(每个处理器的空闲线程的周期时间)

休眠

暂停进程执行给定的秒数。休眠不受系统时间更新的影响。休眠在系统暂停期间暂停。例如,如果一个进程休眠 60 秒,并且系统在休眠中间暂停 30 秒,则实际休眠持续时间为 90 秒。

休眠可以被信号中断:函数以 EINTR 失败。

名称 C 分辨率
nanosleep() 1 纳秒
clock_nanosleep() 1 纳秒
usleep() 1 微秒
delay() 1 微秒
sleep() 1 秒

其他函数

名称 C 分辨率
sigtimedwait() 1 纳秒
pthread_cond_timedwait() 1 纳秒
sem_timedwait() 1 纳秒
select() 1 微秒
epoll() 1 毫秒
poll() 1 毫秒
WaitForSingleObject() 1 毫秒

“C 分辨率”列是底层 C 结构的分辨率。

函数

clock_nanosleep

clock_nanosleep(clock_id, flags, nanoseconds, remaining):Linux clock_nanosleep() 手册页

如果 flags 是 TIMER_ABSTIME,则请求被解释为由时钟 clock_id 测量的绝对时间。如果请求小于或等于时钟的当前值,则 clock_nanosleep() 会立即返回,而不暂停调用线程。

POSIX.1 规定,通过 clock_settime(2) 更改 CLOCK_REALTIME 时钟的值不应对在相对 clock_nanosleep() 上阻塞的线程产生影响。

select()

select(nfds, readfds, writefds, exceptfs, timeout)。

自 Linux 2.6.28 起,select() 使用高分辨率计时器来处理超时。一个进程有一个“松弛”属性来配置超时的精度,默认松弛为 50 微秒。在 Linux 2.6.28 之前,select() 的超时由主计时子系统以 jiffy 级别的分辨率处理。另请阅读 高分辨率(但不要太高)超时计时器松弛

其他函数

  • poll()、epoll()
  • sigtimedwait()。POSIX:“如果支持单调时钟选项,则应使用 CLOCK_MONOTONIC 时钟来测量 timeout 参数指定的时间间隔。”
  • pthread_cond_timedwait()、pthread_condattr_setclock()。“时钟属性的默认值应指系统时间。”
  • sem_timedwait():“如果支持计时器选项,超时将基于 CLOCK_REALTIME 时钟。如果不支持计时器选项,超时将基于 time() 函数返回的系统时间。超时的精度应为基于的时钟的精度。”
  • WaitForSingleObject():使用与 GetTickCount() 相同的计时器,具有相同的精度。

系统待机

ACPI 电源状态“S3”是系统待机模式,也称为“挂起到 RAM”。RAM 保持供电。

在 Windows 上,WM_POWERBROADCAST 消息被发送到 Windows 应用程序,以通知它们电源管理事件(例如,所有者状态已更改)。

对于 Mac OS X,请阅读 注册和注销睡眠和唤醒通知 (Technical Q&A QA1340)。

脚注

接受

该 PEP 于 2012 年 4 月 28 日由 Guido van Rossum [1] 接受。该 PEP 的实现此后已提交到仓库。

参考资料


来源:https://github.com/python/peps/blob/main/peps/pep-0418.rst

最后修改:2025-02-01 08:55:40 GMT