我一直在尝试用Python(3.7)编写一个分辨率至少为毫秒的可靠计时器。目的是每隔几毫秒连续运行一些特定的任务,并且需要长时间。
经过一番研究后,我选择了perf_counter_ns
,因为它具有更高的一致性和 testing 分辨率({{1},monotonic_ns
,time_ns
和{{ 1}}),其详细信息可以在time module documentation和PEP 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
答案 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解释器的时间复杂性的了解。