在单独功能中设置计时器时计时不准确



我的计时器无法可靠工作。我基本上有一个循环,它被设置为尽可能接近计划时间调用函数。除了第一次滴答声外,时间安排都很准确。出于某种原因,当在固定更新之外设置last_time(我也尝试过time.time((、time.process_time(((时(在本例中为start_counting方法(,它是不准确的。

当运行下面的代码时,你会得到这样的东西:

---start---
Time since last 1.5854037 Time accrued by step: 1.6
Time since last 1.5996268 Time accrued by step: 1.6
---start---
Time since last 1.5247734999999998 Time accrued by step: 1.6
Time since last 1.5997646000000003 Time accrued by step: 1.6
Time since last 1.5997717000000007 Time accrued by step: 1.6
Time since last 1.5998178999999997 Time accrued by step: 1.6
Time since last 1.5997903000000004 Time accrued by step: 1.6

正如你所看到的,从我开始计数器到第一次刻度的第一次迭代,时间还差得很远。然而,之后的每一次刻度都像我预期的一样准确~ 1.599~,而初始刻度可以在1.52到1.58之间。我到底错过了什么?时间应该是相同的,因为从开始到步骤重置的步骤是相同的。我觉得我错过了什么,但就我的一生而言,我无法弄清楚。

这是我的代码,可作为Python 3.7运行。也用3.6进行了测试。。

import time
from collections import deque
class FixedStepLoop:
def __init__(self, update_func, step, max_step):
self.update_func = update_func
self.step = step
self.max_step = max_step
self.accumulator = 0.0
self.next_ts = time.perf_counter()
self.last_ts = None
self.cumulative_time = 0
self.times = deque()

def update_time(self):
ts = time.perf_counter()
if self.last_ts is None:
delta_t = 0
else:
delta_t = ts - self.last_ts
self.times.appendleft(delta_t)
if len(self.times) > 10:
self.cumulative_time -= self.times.pop()
self.cumulative_time += delta_t
self.last_ts = ts
return delta_t

def tick(self):
dt = self.update_time()

if dt > self.max_step:
dt = self.max_step
self.accumulator += dt
while self.accumulator >= self.step:
self.update_func(self.step)
self.accumulator -= self.step
tick_count = 16
class FixedCounter:
def __init__(self):
self.steps = None
self.last_time = time.perf_counter()
self.counting = False
self.loop = FixedStepLoop(self.fixed_update, 1/10, 0.2)

def start_counting(self):
print("---start---")
self.steps = 0
self.last_time = time.perf_counter()

def fixed_update(self, dt):
if self.steps is not None:
self.steps += 1

if self.steps == tick_count:
print("Time since last", time.perf_counter() - self.last_time, "Time accrued by step:", self.steps * 0.1)
self.last_time = time.perf_counter()
self.steps = 0

fixed_counter = FixedCounter()
i = 0
while True:
fixed_counter.loop.tick()
i += 1

if i == 8000:
fixed_counter.start_counting()

if i == 2000000:
fixed_counter.start_counting()

非常感谢您的帮助。

问题似乎是,当您调用fixed_counter.start_counting时,您的fixed_counter.loop已经为下一次滴答声积累了一些时间。

因此,在呼叫start_counting之后从tickfixed_update的第一次呼叫发生在对start_counting进行该呼叫之后不到十分之一秒。换句话说,从fixed_counter的角度来看,第一个刻度可以比其他刻度短

事实上,从tickfixed_update的第一次调用应该预期发生在对start_counting的调用之后约0到1/10秒的任何地方。在最好的情况下,调用start_counting之前勾选的最后一次调用将累加器清空到~0,并且累加器仍然需要大约1/10秒才能恢复到大于1/10步长的值。在最坏的情况下,累加器可能在调用start_counting之后立即达到大于(或等于(1/10的值。在这种情况下,在第一次调用fixed_update之前几乎没有经过任何时间。

您可以验证以下修改后的代码是否就是这样。我已经更改了代码,因此对start_counting的调用会导致tick函数打印对start_counting的调用与对start_counting的调用之后的第一个tick之间的时间,然后是对tick的下两个调用之间的时间。

import time
from collections import deque
class FixedStepLoop:
def __init__(self, update_func, step, max_step):
self.update_func = update_func
self.step = step
self.max_step = max_step
self.accumulator = 0.0
self.next_ts = time.perf_counter()
self.last_ts = None
self.cumulative_time = 0
self.times = deque()
self.debug_tick = 0
self.debug_time = None

def update_time(self):
ts = time.perf_counter()
if self.last_ts is None:
delta_t = 0
else:
delta_t = ts - self.last_ts
self.times.appendleft(delta_t)
if len(self.times) > 10:
self.cumulative_time -= self.times.pop()
self.cumulative_time += delta_t
self.last_ts = ts
return delta_t

def tick(self):
dt = self.update_time()

if dt > self.max_step:
dt = self.max_step
self.accumulator += dt

if self.accumulator >= self.step and self.debug_tick:
print("Time to tick: {}".format(time.perf_counter() - self.debug_time))
self.debug_time = time.perf_counter()
self.debug_tick -= 1

while self.accumulator >= self.step:
self.update_func(self.step)
self.accumulator -= self.step
tick_count = 16
class FixedCounter:
def __init__(self):
self.steps = None
self.last_time = time.perf_counter()
self.counting = False
self.loop = FixedStepLoop(self.fixed_update, 1/10, 0.2)

def start_counting(self):
print("---start---")
self.steps = 0
self.last_time = time.perf_counter()
self.loop.debug_tick = 3
self.loop.debug_time = self.last_time

def fixed_update(self, dt):
if self.steps is not None:
self.steps += 1

if self.steps == tick_count:
print("Time since last", time.perf_counter() - self.last_time, "Time accrued by step:", self.steps * 0.1)
self.last_time = time.perf_counter()
self.steps = 0

fixed_counter = FixedCounter()
i = 0
while True:
fixed_counter.loop.tick()
i += 1

if i == 8000:
fixed_counter.start_counting()

if i == 2000000:
fixed_counter.start_counting()

您应该看到类似以下输出的内容。请注意,第一次勾选的时间明显小于第二次和第三次。

---start---
Time to tick: 0.08820800000000001
Time to tick: 0.09974270000000002
Time to tick: 0.09983560000000002
Time since last 1.5882072 Time accrued by step: 1.6
Time since last 1.5997456999999997 Time accrued by step: 1.6
---start---
Time to tick: 0.0507023000000002
Time to tick: 0.09982599999999975
Time to tick: 0.09973420000000033
Time since last 1.5507022999999998 Time accrued by step: 1.6
Time since last 1.5997678000000004 Time accrued by step: 1.6
Time since last 1.5997316999999995 Time accrued by step: 1.6

如果您需要更高的精度,您应该减少用于FixedStepLoopstepmax_step,并相应地增加tick_count。例如,我通过将stepmax_steptick_count分别更改为0.010.021600来获得以下输出。

---start---
Time since last 1.5979447999999998 Time accrued by step: 1.6
---start---
Time since last 1.5954332000000004 Time accrued by step: 1.6
Time since last 1.5997648999999994 Time accrued by step: 1.6
Time since last 1.5997534 Time accrued by step: 1.6

当然,第一次和随后打印消息的时间之间仍然存在差异,但您应该能够更改参数,使差异任意降低。

最新更新