【问题标题】:Inaccurate while loop timing in PythonPython中的while循环计时不准确
【发布时间】:2018-05-22 15:53:54
【问题描述】:

我正在尝试使用 Raspberry Pi 3 B+ 以高采样率记录数据。为了达到固定的采样率,我延迟了 while 循环,但我得到的采样率总是比我指定的小一点。

对于 2500 Hz,我得到 ~2450 Hz

对于 5000 Hz,我得到 ~4800 Hz

对于 10000 Hz,我得到 ~9300 Hz

这是我用来延迟 while 循环的代码:

import time

count=0
while True:
    sample_rate=5000

    time_start=time.perf_counter()
    count+=1
    while (time.perf_counter()-time_start) < (1/sample_rate):
        pass

    if count == sample_rate:
        print(1/(time.perf_counter()-time_start))
        count=0

我也尝试过更新到 Python 3.7 并使用了 time.perf_counter_ns(),但这并没有什么不同。

【问题讨论】:

  • 执行 while 循环需要时间。由于 Python 是一种解释型语言,因此在解释代码时会产生开销。如果你想达到更高的精度,最好用编译语言编写采样代码。
  • 为什么要紧循环,为什么不只是延迟呢?而且,如果您在开始循环之前读取时间,然后每次都以您想要的延迟增加它,那么抖动只会在正确的间隔附近,不会因为每次循环读取时间而导致额外的不确定性。但是是的,Pi 上的 linux 并不完全是一个实时平台,你的时间会有所不同。

标签: python raspberry-pi


【解决方案1】:

您看到的问题是因为您的代码每次在循环中开始每次延迟时都使用实时时间 - 因此在不定时代码中花费的时间和由于操作系统多任务处理引起的抖动会累积,从而降低整体低于你想要达到的时间。

要大大提高计时精度,请使用每个循环“应该”在应该开始后的周期 (1/sample_rate) 完成这一事实 - 并将该开始时间保持为绝对计算而不是实时,并且等到那个绝对开始时间之后的那段时间,然后时间就没有漂移了。

我把你的时间放到了timing_orig中,我使用绝对时间的修改后的代码放到了timing_new中——结果如下。

import time

def timing_orig(ratehz,timefun=time.clock):
    count=0
    while True:
        sample_rate=ratehz

        time_start=timefun()
        count+=1
        while (timefun()-time_start) < (1.0/sample_rate):
            pass

        if count == ratehz:
            break

def timing_new(ratehz,timefun=time.clock):
    count=0
    delta = (1.0/ratehz)
    # record the start of the sequence of timed periods
    time_start=timefun()
    while True:
        count+=1
        # this period ends delta from "now" (now is the time_start PLUS  a number of deltas)
        time_next = time_start+delta
        # wait until the end time has passed
        while timefun()<time_next:
            pass
        # calculate the idealised "now" as delta from the start of this period
        time_start = time_next
        if count == ratehz:
            break

def timing(functotime,ratehz,ntimes,timefun=time.clock):
    starttime = timefun()
    for n in range(int(ntimes)):
        functotime(ratehz,timefun)
    endtime = timefun()
#   print endtime-starttime
    return ratehz*ntimes/(endtime-starttime)

if __name__=='__main__':
    print "new 5000",timing(timing_new,5000.0,10.0)
    print "old 5000",timing(timing_orig,5000.0,10.0)
    print "new 10000",timing(timing_new,10000.0,10.0)
    print "old 10000",timing(timing_orig,10000.0,10.0)
    print "new 50000",timing(timing_new,50000.0,10.0)
    print "old 50000",timing(timing_orig,50000.0,10.0)
    print "new 100000",timing(timing_new,100000.0,10.0)
    print "old 100000",timing(timing_orig,100000.0,10.0)

结果:

new 5000 4999.96331002
old 5000 4991.73952992
new 10000 9999.92662005
old 10000 9956.9314274
new 50000 49999.6477761
old 50000 49591.6104893
new 100000 99999.2172809
old 100000 94841.227219

注意我没有使用 time.sleep() 因为它引入了太多的抖动。另外,请注意,即使这个最小的示例在我的 Windows 笔记本电脑上显示了非常准确的时序(甚至高达 100khz),但如果您将更多的代码放入循环中而不是执行时间,那么时序会相应地运行缓慢。

抱歉,我使用了 Python 2.7,它没有非常方便的 time.perf_counter() 函数 - 在对 timing() 的每个调用中添加一个额外的参数 timefun=time.perf_counter()

【讨论】:

    【解决方案2】:

    我认为您可以通过重新排列代码来轻松解决此问题:

    import time
    
    count=0 
    sample_rate=5000
    while True:
        time_start=time.perf_counter()
    
        # do all the real stuff here
    
        while (time.perf_counter()-time_start) < (1/sample_rate):
            pass
    

    这样,python 在您执行代码之后而不是之前进行等待,因此解释器运行它所花费的时间不会被添加到您的采样率中。正如 danny 所说,它是一种解释性语言,因此可能会引入时间不一致,但这种方式至少应该会减少您看到的效果。

    编辑以证明这是有效的:

    import sys
    import time
    
    count=0 
    sample_rate=int(sys.argv[1])
    run_start = time.time()
    
    while True:
        time_start=time.time()
    
        a = range(10)
        b = range(10)
        for x in a:
            for y in b:
                c = a+b
        count += 1
        if count == sample_rate*2:
            break
        while (time.time()-time_start) < (1.0/sample_rate):
            pass
    
    real_rate = sample_rate*2/(time.time()-run_start)
    print real_rate, real_rate/sample_rate     
    

    因此,测试代码会在 2 秒内执行大量随机垃圾,然后打印实际速率和实际速率的百分比。以下是一些结果:

    ~ ><> python t.py 1000
    999.378471674 0.999378471674
    ~ ><> python t.py 2000
    1995.98713838 0.99799356919
    ~ ><> python t.py 5000
    4980.90553757 0.996181107514
    ~ ><> python t.py 10000
    9939.73553783 0.993973553783
    ~ ><> python t.py 40000
    38343.706669 0.958592666726  
    

    所以,并不完美。但绝对比在所需的 10000 处下降约 700Hz 好。公认的答案绝对是正确的。

    【讨论】:

    • 你能证明你的重新排列实际上产生了更准确的计时吗?更准确多少?
    猜你喜欢
    • 2017-10-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-02
    • 2023-03-10
    • 1970-01-01
    • 2017-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多