【问题标题】:Do Something At a Consistent Frequency in Python在 Python 中以一致的频率做某事
【发布时间】:2020-12-13 10:53:11
【问题描述】:

我有一个 python 程序,它从串行端口读取数据,访问寄存器然后将数据写入 CSV。我想以 100Hz 的设定频率对 csv 执行写入操作。下面是与尝试限制写入 csv 的时间相关的示例代码。为简单起见,它只会打印到控制台而不是写入 csv。

import datetime
from datetime import timezone 
import time

FREQ = 5
CYCLETIME = 1/FREQ

def main():
    
    while(1):
        ### Code that will read message bytes from a port

        # Time start for Hz
        start = time.monotonic() 
        delta = time.monotonic() - start
        if delta < CYCLETIME:
            time.sleep(CYCLETIME - delta)

        # in the full program we write to a csv but in this simple program we will just print it
        milliseconds_since_epoch = datetime.datetime.now(timezone.utc)
        print(milliseconds_since_epoch)

if __name__ == "__main__":
    main()
2020-08-24 18:57:15.572637+00:00
2020-08-24 18:57:15.773183+00:00
2020-08-24 18:57:15.973637+00:00
2020-08-24 18:57:16.174117+00:00
2020-08-24 18:57:16.374569+00:00
2020-08-24 18:57:16.575058+00:00
2020-08-24 18:57:16.775581+00:00
2020-08-24 18:57:16.976119+00:00
2020-08-24 18:57:17.176627+00:00
2020-08-24 18:57:17.377103+00:00
2020-08-24 18:57:17.577556+00:00

5Hz 的输出似乎一致,但如果我将其更改为 100Hz,它似乎不一致。听起来这可能是与 time.monotonic 相关的时间精度漂移。这是一个好方法吗? time.montonic 合适吗?

我对 pythons 线程库的了解有限,但在不久的将来,我计划为每个任务创建 2 个子线程。一个不断从串行读取,另一个将写入(或者在我们的例子中,每 100Hz 打印到控制台)。

编辑: 我采用了下面的解决方案并对其进行了修改。最初的解决方案似乎只打印一次。这是我的新尝试:

import datetime
from datetime import timezone 
import time

FREQ = 5
CYCLETIME = 1/FREQ

def main():
    t0 = time.perf_counter()    # Time ref point in ms
    time_counter = t0           # Will be incremented with CYCLETIME for each iteration

    while 1:
        ### Code that will read message bytes from a port

        now = time.perf_counter()
        elapsed_time = now - time_counter
        if elapsed_time < CYCLETIME:
            target_time =  CYCLETIME - elapsed_time
            time.sleep(target_time)
           
        # In the full program we write to a csv but in this simple program we will just print it
        milliseconds_since_epoch = datetime.datetime.now(timezone.utc)
        print(milliseconds_since_epoch)

        time_counter += CYCLETIME

if __name__ == "__main__":
    main()

输出:

我使用 matplot lib 来确定创建此输出的频率。我取当前值和前一个值的滚动窗口差,并从频率 = 1/(时间差)开始反转它。

【问题讨论】:

    标签: python-3.x multithreading time


    【解决方案1】:

    你的算法有 5 个问题:

    • 使用time.sleep() 的精度较低,有时会出现10-13ms 左右的误差,具体取决于您的系统。 (ref)
    • startdelta 变量不会跟踪在 print() 上花费的时间,因为 delta 设置在 start 之后。
    • startdelta 变量使用两个不同的 time.monotonic()。您应该只调用该函数一次,然后将值传递给另一个变量,以确保使用相同的时间值。
    • time.monotonic() 的滴答率为每秒 64 次。请改用time.perf_counter()。 (ref)
    • 您在增量时间上执行time.sleep(),精度较低。如果您有两个线程一起运行,则周期将很难同步。您需要一个时间跟踪器,让它根据经过的时间进入睡眠状态。

    修复:

    以下代码使用time.perf_counter(),并使用time_counter 来跟踪之前的循环迭代时间戳。

    下一个循环时间是previous loop timestamp + cycle time - elapsed time。因此,我们将确保time.sleep() 让程序休眠直到那时。

    def main():
        t0 = time.perf_counter()  # Time ref point
        time_counter = t0  # Will be incremented with CYCLETIME for each iteration
    
        while 1:
            ### Code that will read message bytes from a port
    
            now = time.perf_counter()
            elapsed_time = now - t0
            target_time = time_counter + CYCLETIME
            if elapsed_time < target_time:
                time.sleep(target_time - elapsed_time)
    
            # In the full program we write to a csv but in this simple program we will just print it
            milliseconds_since_epoch = datetime.datetime.now(timezone.utc)
            print(milliseconds_since_epoch)
    
            time_counter += CYCLETIME
    

    【讨论】:

    • 我会把它改编成代码,如果我有任何问题,请告诉你。
    • 我不得不进行一些修改,因为您发布的代码只打印一次 (hatebin.com/idcsekwbii)。想法?如果正确,请修改您的答案,我会将解决方案标记为正确。既然您首先发布了解决方案,我想在您将我引向正确方向时奖励您答案。
    • 我添加了一个图表。对于 100 Hz,时间似乎仍然有很大的偏差。
    • 正如我在答案中提到的那样, time.sleep() 的准确性较低。阅读更多here。我为您所做的是确保程序以正确的时间间隔运行。这样,在运行程序 100 秒后,您就知道它已经循环了 10000 次。尽管如此,由于您的操作系统没有进行准确的睡眠,每次迭代都会有一点偏差。
    • 如果你真的想做准确的计时,考虑不做睡眠,并使用 perf_counter 检查每次循环迭代的时间。但是,正如您所料,这种方法会带来更多的性能负担。
    猜你喜欢
    • 1970-01-01
    • 2016-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多