在单独的函数中设置定时器时计时不准确
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
之后从 tick
到 fixed_update
的第一次调用发生在调用 start_counting
之后不到十分之一秒。也就是说,从fixed_counter
的角度来看,第一个tick可以比其他的短
事实上,从 tick
到 fixed_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
如果您需要更高的精度,您应该减少用于 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
当然,消息打印的第一次和随后的时间还是有区别的,但是你应该可以改变参数来使这种差异任意小。
我在让我的计时器可靠工作时遇到了问题。我基本上有一个循环,设置为尽可能接近预定时间调用函数。除了第一个滴答声外,时间安排足够准确。出于某种原因,在固定更新之外设置 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可以比其他的短
事实上,从 tick
到 fixed_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
如果您需要更高的精度,您应该减少用于 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
当然,消息打印的第一次和随后的时间还是有区别的,但是你应该可以改变参数来使这种差异任意小。