我有一个 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)
这种倾向遵循的一般行为是基本上不会等待,除非在运行该函数之前暂停一会儿,在这种情况下会出现过度等待。
你在使用 Cython 时遇到的问题,主要是由于在 Cython 中 float 类型的精度不足而引起的。以下是问题的具体原因和解决方法。
float
Cython 的 float 是单精度浮点数 在你的代码中,cdef float wait_time 和 cdef float end_time 被声明为 C 的 float 类型,这意味着它们是 32 位单精度浮点数。相比之下,Python 的 float 实际上是 64 位双精度浮点数。 单精度浮点数精度较低,可能导致 time.time() 返回的时间戳在加法运算中出现微小的精度损失。
cdef float wait_time
cdef float end_time
time.time()
循环条件的影响 当 time.time() 被多次调用,并与可能有精度损失的 end_time 比较时,会导致不一致的行为,尤其是在高频调用 time.time() 的情况下,误差会被放大。
end_time
%timeit 的干扰 %timeit 在测试时可能会引入额外的调度延迟或线程切换。这可能会进一步放大计时的不准确性,尤其是在你的代码中包含紧密的 while 循环的情况下。
%timeit
while
为了避免精度问题,应该使用 C 的 double 类型来表示浮点数,与 Python 的 float 保持一致。修改后的代码如下:
double
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")
cdef double 替代 cdef float C 的 double 是 64 位双精度浮点数,与 Python 的 float 保持一致,避免因精度损失导致的不稳定行为。
cdef double
cdef float
更精准的时间比较 通过提高精度,可以确保 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 秒。
如果你的实际代码不需要频繁轮询时间戳,可以在 while 循环中加入短暂的休眠,例如:
while time.time() < end_time: time.sleep(0.001) # 每次休眠 1 毫秒,减少 CPU 使用
这不仅可以降低 CPU 使用率,还可以提升程序的整体效率。
你的问题源于 Cython 中 float 类型的精度不足。通过使用 double 类型和适当优化,计时行为可以变得准确且一致。如果有更多需要优化的地方,请随时询问!