我的计时器无法可靠工作。我基本上有一个循环,它被设置为尽可能接近计划时间调用函数。除了第一次滴答声外,时间安排都很准确。出于某种原因,当在固定更新之外设置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
之后从tick
到fixed_update
的第一次呼叫发生在对start_counting
进行该呼叫之后不到十分之一秒。换句话说,从fixed_counter
的角度来看,第一个刻度可以比其他刻度短
事实上,从tick
到fixed_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
如果您需要更高的精度,您应该减少用于FixedStepLoop
的step
和max_step
,并相应地增加tick_count
。例如,我通过将step
、max_step
和tick_count
分别更改为0.01
、0.02
和1600
来获得以下输出。
---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
当然,第一次和随后打印消息的时间之间仍然存在差异,但您应该能够更改参数,使差异任意降低。