为什么Python的时间函数(例如perf_counter_ns)的解析不一致?

时间:2019-07-29 07:03:47

标签: python raspberry-pi counter clock

背景

我一直在尝试用Python(3.7)编写一个分辨率至少为毫秒的可靠计时器。目的是每隔几毫秒连续运行一些特定的任务,并且需要长时间。 经过一番研究后,我选择了perf_counter_ns,因为它具有更高的一致性和 testing 分辨率({{1},monotonic_nstime_ns和{{ 1}}),其详细信息可以在time module documentationPEP 564

中找到

测试

为了确保perf_counter_ns的精度(和准确性),我设置了一个测试来收集连续时间戳之间的延迟,如下所示。

process_time_ns

问题和疑问

问题:为什么在时间戳之间偶尔会有明显的跳过 在我的Raspberry Pi 3 Model B V1.2上进行的具有10,000,000个计数的多次测试产生了相似的结果,如下所示(时间单位当然是毫微秒):

thread_time_ns

在Windows桌面上进行的另一项测试:

import time
import statistics as stats
# import resource

def practical_res_test(clock_timer_ns, count, expected_res):
  counter = 0
  timestamp = clock_timer_ns() # initial timestamp
  diffs = []
  while counter < count:
    new_timestamp = clock_timer_ns()
    diff = new_timestamp - timestamp
    if (diff > 0):
      diffs.append(diff)
      timestamp = new_timestamp
      counter += 1

  print('Mean: ', stats.mean(diffs))
  print('Mode: ', stats.mode(diffs))
  print('Min: ', min(diffs))
  print('Max: ', max(diffs))

  outliers = list(filter(lambda diff: diff >= expected_res, diffs))
  print('Outliers Total: ', len(outliers))

if __name__ == '__main__':

  count = 10000000

  # ideally, resolution of at least 1 ms is expected
  # but let's just do 10 ms for the sake of this test
  expected_res = 10000 

  practical_res_test(time.perf_counter_ns, count)

  # other method benchmarks
  # practical_res_test(time.time_ns, count)
  # practical_res_test(time.process_time_ns, count)
  # practical_res_test(time.thread_time_ns, count)
  # practical_res_test(
  #   lambda: int(resource.getrusage(resource.RUSAGE_SELF).ru_stime * 10**9),
  #   count
  # )

尽管我知道分辨率在不同的系统上会有所不同,但很容易注意到与PEP 564中的等级相比,我的测试中的分辨率要低得多。最重要的是,偶尔会出现跳过现象。

如果您对这种情况的发生有任何了解,请与我联系。与我的测试有关系吗,还是在这种用例中perf_counter_ns一定会失败?如果是这样,您对更好的解决方案有何建议? 让我知道是否需要提供其他信息。

其他信息

为完成此操作,以下是time.get_clock_info()中的时钟信息

在我的树莓派上:

Mean:  2440.1013097
Mode:  2396
Min:  1771
Max:  1450832 # huge skip as I mentioned
Outliers Total:  8724 # delays that are more than 10 ms

在我的Windows桌面上:

Mean:  271.05812 # higher end machine - better resolution
Mode:  200
Min:  200
Max:  30835600 # but there is still skips, even more significant
Outliers Total:  49021

值得一提的是,我对Clock: perf_counter Adjustable: False Implementation: clock_gettime(CLOCK_MONOTONIC) Monotonic: True Resolution(ns): 1 有所了解,但是从我的测试和用例来看,它并不是特别可靠,因为其他人已经讨论了here

1 个答案:

答案 0 :(得分:1)

如果绘制时差列表,您会看到基线较低,且峰值随时间增加。

这是由append()操作引起的,该操作有时不得不重新分配基础数组(这是实现Python列表的方式)。 通过预分配数组,结果将得到改善:

import time
import statistics as stats
import gc
import matplotlib.pyplot as plt

def practical_res_test(clock_timer_ns, count, expected_res):
  counter = 0
  diffs = [0] * count
  gc.disable()
  timestamp = clock_timer_ns() # initial timestamp
  while counter < count:
    new_timestamp = clock_timer_ns()
    diff = new_timestamp - timestamp
    if diff > 0:
      diffs[counter] = diff
      timestamp = new_timestamp
      counter += 1
  gc.enable()
  print('Mean: ', stats.mean(diffs))
  print('Mode: ', stats.mode(diffs))
  print('Min: ', min(diffs))
  print('Max: ', max(diffs))

  outliers = list(filter(lambda diff: diff >= expected_res, diffs))
  print('Outliers Total: ', len(outliers))
  plt.plot(diffs)
  plt.show()

if __name__ == '__main__':

  count = 10000000

  # ideally, resolution of at least 1 us is expected
  # but let's just do 10 us for the sake of this test
  expected_res = 10000 

  practical_res_test(time.perf_counter_ns, count, expected_res)

这些是我得到的结果:

Mean:  278.6002
Mode:  200
Min:  200
Max:  1097700
Outliers Total:  3985

相比之下,这些是我系统上原始代码的结果:

Mean:  333.92254
Mode:  300
Min:  200
Max:  50507300
Outliers Total:  2590

要获得更好的性能,您可能希望在Linux上运行并使用SCHED_FIFO。但是请始终记住,微秒级的实时任务不是在Python中完成的。 如果您的问题是实时的,那么您可以解决它,但这完全取决于错过最后期限的惩罚以及您对代码和Python解释器的时间复杂性的了解。