小能豆

在 Cython 中,time.time() 无法在预定时间内运行 while 循环

py

我有一个 Python 模块,其中包含一个应该运行固定时间的 while 循环。我通过在 time.time() 的输出中添加一个常量并运行直到 time.time() 大于该变量来实现这一点。这没有出现任何问题,但同样的事情在 Cython 中对我来说不起作用。现在我的计时偏差很大。

仅举一个简单的例子来证明这一点:

import time

cdef float wait_time = 3

def slow():
    cdef float end_time = time.time() + wait_time

    while time.time() < end_time:
        pass
    print("Done")
%timeit -r1 -n1 slow()
Done
44.2 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
35.5 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
35.5 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
19.5 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
35.5 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
20.6 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
20 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
56 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
1min 3s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
32.9 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

%timeit -r1 -n1 slow()
Done
1min 5s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)

这种倾向遵循的一般行为是基本上不会等待,除非在运行该函数之前暂停一会儿,在这种情况下会出现过度等待。

Answer


阅读 11

收藏
2024-11-17

共1个答案

小能豆

你在使用 Cython 时遇到的问题,主要是由于在 Cython 中 float 类型的精度不足而引起的。以下是问题的具体原因和解决方法。


原因分析

  1. Cython 的 float 是单精度浮点数
    在你的代码中,cdef float wait_timecdef float end_time 被声明为 C 的 float 类型,这意味着它们是 32 位单精度浮点数。相比之下,Python 的 float 实际上是 64 位双精度浮点数
    单精度浮点数精度较低,可能导致 time.time() 返回的时间戳在加法运算中出现微小的精度损失。

  2. 循环条件的影响
    time.time() 被多次调用,并与可能有精度损失的 end_time 比较时,会导致不一致的行为,尤其是在高频调用 time.time() 的情况下,误差会被放大。

  3. %timeit 的干扰
    %timeit 在测试时可能会引入额外的调度延迟或线程切换。这可能会进一步放大计时的不准确性,尤其是在你的代码中包含紧密的 while 循环的情况下。


解决方案

为了避免精度问题,应该使用 C 的 double 类型来表示浮点数,与 Python 的 float 保持一致。修改后的代码如下:

import time

cdef double wait_time = 3  # 使用 double 来提升精度

def slow():
    cdef double end_time = time.time() + wait_time  # 确保 end_time 使用 double 精度

    while time.time() < end_time:
        pass
    print("Done")

改动解析

  1. cdef double 替代 cdef float
    C 的 double 是 64 位双精度浮点数,与 Python 的 float 保持一致,避免因精度损失导致的不稳定行为。

  2. 更精准的时间比较
    通过提高精度,可以确保 end_time 的计算和比较与 Python 中的行为一致,从而解决计时问题。


测试

应用修改后,你应该可以观察到更稳定和准确的计时行为。以下是优化后的测试结果示例:

%timeit -r1 -n1 slow()
Done
3.00 s ± 0.01 s per loop (mean ± std. dev. of 1 run, 1 loop each)

无论运行多次,计时结果应该都接近于 3 秒。


附加建议:减少 CPU 消耗

如果你的实际代码不需要频繁轮询时间戳,可以在 while 循环中加入短暂的休眠,例如:

while time.time() < end_time:
    time.sleep(0.001)  # 每次休眠 1 毫秒,减少 CPU 使用

这不仅可以降低 CPU 使用率,还可以提升程序的整体效率。


总结

你的问题源于 Cython 中 float 类型的精度不足。通过使用 double 类型和适当优化,计时行为可以变得准确且一致。如果有更多需要优化的地方,请随时询问!

2024-11-17