【问题标题】:datetime timestamp using Python with microsecond level accuracy使用 Python 的日期时间时间戳,精度为微秒级
【发布时间】:2022-02-26 20:56:20
【问题描述】:

我正在尝试在 Python 3.10+ 的 Windows 操作系统和 macOS 上获得精确到微秒的时间戳。

在 Windows 操作系统上,我注意到 Python 的内置 time.time()(与 datetime.fromtimestamp() 配对)和 datetime.datetime.now() 似乎有一个较慢的时钟。它们没有足够的分辨率来区分微秒级事件。好消息是 time 函数,如 time.perf_counter()time.time_ns() 确实 似乎使用的时钟速度足够快,可以测量微秒级事件。

遗憾的是,我不知道如何将它们放入 datetime 对象中。如何将time.perf_counter()PEP 564 的纳秒分辨率时间函数的输出转换为datetime 对象?

注意:我不需要纳秒级的东西,所以扔掉低于 1-μs 的精度是可以的)。


当前解决方案

这是我当前的(hacky)解决方案,实际上效果很好,但我想知道是否有更清洁的方法:

import time
from datetime import datetime, timedelta
from typing import Final

IMPORT_TIMESTAMP: Final[datetime] = datetime.now()
INITIAL_PERF_COUNTER: Final[float] = time.perf_counter()


def get_timestamp() -> datetime:
    """Get a high resolution timestamp with μs-level precision."""
    dt_sec = time.perf_counter() - INITIAL_PERF_COUNTER
    return IMPORT_TIMESTAMP + timedelta(seconds=dt_sec)

【问题讨论】:

  • 不可能使用time.perf_counter 的输出来获得绝对时间。来自docs:“返回值的参考点是未定义的,所以只有两次调用结果的差异才有效。”
  • @DeepSpace 请查看更新后的问题。可以适应工作

标签: python datetime time timestamp


【解决方案1】:

这几乎和它得到的一样好,因为C 模块(如果可用)使用快速 C 实现覆盖在 datetime 模块的纯 Python 实现中定义的所有类,并且有没有钩子。
参考:python/cpython@cf86e36

注意:

  1. 在精度中存在一个固有的亚微秒级误差,它等于在 datetime.now() 中获取系统时间与获取性能计数器时间之间所用的时间。
  2. 添加datetimetimedelta 会产生亚微秒级的性能成本。

根据您的具体用例是否多次调用,这可能会或可能不会重要。

稍有改进:

INITIAL_TIMESTAMP: Final[float] = time.time()
INITIAL_TIMESTAMP_PERF_COUNTER: Final[float] = time.perf_counter()

def get_timestamp_float() -> float:
    dt_sec = time.perf_counter() - INITIAL_TIMESTAMP_PERF_COUNTER
    return INITIAL_TIMESTAMP + dt_sec

def get_timestamp_now() -> datetime:
    dt_sec = time.perf_counter() - INITIAL_TIMESTAMP_PERF_COUNTER
    return datetime.fromtimestamp(INITIAL_TIMESTAMP + dt_sec)

轶事数字

窗户:

# Intrinsic error
timeit.timeit('datetime.now()', setup='from datetime import datetime')/1000000  # 0.31 μs
timeit.timeit('time.time()', setup='import time')/1000000                       # 0.07 μs

# Performance cost
setup = 'from datetime import datetime, timedelta; import time'
timeit.timeit('datetime.now() + timedelta(1.000001)', setup=setup)/1000000            # 0.79 μs
timeit.timeit('datetime.fromtimestamp(time.time() + 1.000001)', setup=setup)/1000000  # 0.44 μs
# Resolution
min get_timestamp_float() delta: 239 ns

Windows 和 macOS:

Windows macOS
# Intrinsic error
timeit.timeit('datetime.now()', setup='from datetime import datetime')/1000000 0.31 μs 0.61 μs
timeit.timeit('time.time()', setup='import time')/1000000 0.07 μs 0.08 μs
# Performance cost
setup = 'from datetime import datetime, timedelta; import time' - -
timeit.timeit('datetime.now() + timedelta(1.000001)', setup=setup)/1000000 0.79 μs 1.26 μs
timeit.timeit('datetime.fromtimestamp(time.time() + 1.000001)', setup=setup)/1000000 0.44 μs 0.69 μs
# Resolution
min time() delta (benchmark) x ms 716 ns
min get_timestamp_float() delta 239 ns 239 ns

239 ns 是 float 在 Unix 时间量级上允许的最小差异,正如 cmets 中的 Kelly Bundy 所指出的那样。

x = time.time()
print((math.nextafter(x, 2*x) - x) * 1e9)  # 238.4185791015625

脚本

解析脚本,基于https://www.python.org/dev/peps/pep-0564/#script:

import math
import time
from typing import Final

LOOPS = 10 ** 6

INITIAL_TIMESTAMP: Final[float] = time.time()
INITIAL_TIMESTAMP_PERF_COUNTER: Final[float] = time.perf_counter()

def get_timestamp_float() -> float:
    dt_sec = time.perf_counter() - INITIAL_TIMESTAMP_PERF_COUNTER
    return INITIAL_TIMESTAMP + dt_sec

min_dt = [abs(time.time() - time.time())
          for _ in range(LOOPS)]
min_dt = min(filter(bool, min_dt))
print("min time() delta: %s ns" % math.ceil(min_dt * 1e9))

min_dt = [abs(get_timestamp_float() - get_timestamp_float())
          for _ in range(LOOPS)]
min_dt = min(filter(bool, min_dt))
print("min get_timestamp_float() delta: %s ns" % math.ceil(min_dt * 1e9))

【讨论】:

    猜你喜欢
    • 2014-09-20
    • 2012-09-17
    • 1970-01-01
    • 1970-01-01
    • 2016-07-14
    • 1970-01-01
    • 2017-08-11
    • 2014-10-21
    • 1970-01-01
    相关资源
    最近更新 更多