在单独的函数中设置定时器时计时不准确

Inaccurate timings when timer is set in separate function

我在让我的计时器可靠工作时遇到了问题。我基本上有一个循环,设置为尽可能接近预定时间调用函数。除了第一个滴答声外,时间安排足够准确。出于某种原因,在固定更新之外设置 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的角度来看,第一个tick可以比其他的短

事实上,从 tickfixed_update 的第一次调用应该发生在调用 start_counting 之后的 ~0 到 1/10 秒之间的任何时间。在最好的情况下,最后一次调用 tick before 调用 start_counting 将累加器清空到 ~0,累加器仍然需要大约 1/10 秒才能恢复到一个大于我们 1/10 步长的值。在最坏的情况下,在 调用 start_counting 之后,累加器可能会立即达到大于(或等于)1/10 的值 。在这种情况下,在第一次调用 fixed_update.

之前几乎没有时间过去

您可以验证下面修改后的代码是否会发生这种情况。我更改了代码,以便对 start_counting 的调用导致 tick 函数打印对 start_counting 的调用与第一个 tick 之间的时间start_counting 的调用,随后是对 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

当然,消息打印的第一次和随后的时间还是有区别的,但是你应该可以改变参数来使这种差异任意小。