一个简单的 Python 循环耗时测量工具
在日常开发中,我们经常需要测量某段代码的循环耗时或计算帧率。传统做法通常需要手动记录时间、计算差值、再额外计算平均值或帧率,这不仅容易出错,而且代码冗长、重复。尤其是在实时或高频任务中,手动计算每一帧耗时和帧率会显得非常繁琐。
开发 LoopTick 的初衷,就是为了简化这种重复的计时工作,让开发者可以专注于业务逻辑,而不用每次都写繁琐的时间计算。LoopTick 提供了统一、轻量且高精度的循环计时接口,自动计算实时帧率和平均帧率,减少手动错误,同时代码更清晰、更易维护。
它不仅适合普通 Python 循环,也非常适合实时仿真、游戏循环、数据采集或其他需要精确时间统计的场景,让开发者能够快速了解循环性能和优化瓶颈。
import time from looptick import LoopTick def work(): """模拟工作负载""" time.sleep(0.001) ## 手动计时方式 print("=== 手动计时 ===") start_total = time.time() times = [] for i in range(5): start = time.time() work() elapsed = time.time() - start times.append(elapsed) hz = 1 / elapsed if elapsed > 0 else 0 # 计算实时平均Hz avg_elapsed = sum(times) / len(times) avg_hz = 1 / avg_elapsed if avg_elapsed > 0 else 0 print(f"第{i+1}次: {(elapsed * 1000):.2f}ms, {hz:.2f}Hz, 平均Hz: {avg_hz:.2f}") total_manual = (time.time() - start_total) * 1000 avg_manual = (sum(times) / len(times)) * 1000 avg_hz_manual = 1 / (sum(times) / len(times)) print(f"总耗时: {total_manual:.2f}ms, 平均: {avg_manual:.2f}ms, 平均Hz: {avg_hz_manual:.2f}Hz\n")
import time from looptick import LoopTick def work(): """模拟工作负载""" time.sleep(0.001) ## 使用LoopTick print("=== 使用LoopTick ===") loop = LoopTick() for i in range(5): diff = loop.tick() # 获取上一次调用 tick() 的用时 (ns), 第一次调用 tick() 返回一个极小值 (0.001, 可手动设置), work() # 模拟工作负载 hz = loop.get_hz() # 获取当前帧率 (Hz), 第一次调用 tick() 返回的是预设值 1 avg_hz = loop.get_avg_hz() # 获取实时平均帧率 (Hz), 第第一次调用 tick() 默认返回 1 print(f"第{i+1}次: {diff * loop.NS2MS:.2f}ms, {hz:.2f}Hz, 平均Hz: {avg_hz:.2f}") print(f"总耗时: {loop.total_ms:.2f}ms, 平均: {loop.avg_ms:.2f}ms, 平均Hz: {loop.get_avg_hz():.2f}Hz\n")
两种输出结果示例:
(LoopTick) PS C:\IT\LoopTick> & C:\IT\LoopTick\.venv\Scripts\python.exe c:/IT/LoopTick/examples/with_usage.py === 手动计时 === 第1次: 1.06ms, 944.45Hz, 平均Hz: 944.45 第2次: 1.06ms, 947.87Hz, 平均Hz: 946.15 第3次: 1.05ms, 948.94Hz, 平均Hz: 947.08 第4次: 1.05ms, 950.01Hz, 平均Hz: 947.81 第5次: 1.11ms, 900.26Hz, 平均Hz: 937.90 总耗时: 5.38ms, 平均: 1.07ms, 平均Hz: 937.90Hz === 使用LoopTick === 第1次: 0.00ms, 0.00Hz, 平均Hz: 100000000000.00 第2次: 1.07ms, 937.33Hz, 平均Hz: 937.33 第3次: 1.06ms, 944.60Hz, 平均Hz: 940.95 第4次: 1.06ms, 947.34Hz, 平均Hz: 943.07 第5次: 1.09ms, 917.11Hz, 平均Hz: 936.44 总耗时: 4.27ms, 平均: 1.07ms, 平均Hz: 936.44Hz
上述代码显示了使用 LoopTick 相比传统手动计时的优势:
- 简化代码:不需要每次手动记录
start/end、计算耗时和帧率,LoopTick 内部自动完成tick()、get_hz()和get_avg_hz()的计算。 - 实时统计:每次循环都能直接获取当前帧耗时、当前帧率和平均帧率,无需额外累加计算。
- 高精度:LoopTick 使用纳秒级计时,精度更高,尤其在短时间间隔的循环中更稳定。
- 总耗时和平均耗时:通过
loop.total_ms和loop.avg_ms一行即可获得整个循环的总耗时和平均耗时,省去了多行累加逻辑。
LoopTick 在减少重复计算和冗余代码的同时,保持了统计结果的一致性,并且接口更加直观易用。
从 PyPi
pip install looptick
本地安装
git clone https://github.com/DBinK/LoopTick
pip install -e .常规用法
from looptick import LoopTick import time looptick = LoopTick() # 常规调用方式 for i in range(5): diff = looptick.tick() print(f"第 {i} 次循环耗时: {diff * looptick.NS2MS:.6f} ms") time.sleep(0.01) print(f"总耗时: {looptick.total_sec:.6f} 秒") print(f"平均耗时: {looptick.average_ms:.6f} ms") # 或者用更精简的语法 for i in range(5): diff = looptick() # 直接调用 __call__() 方法, 免去书写 tick() print(f"第 {i} 次循环耗时: {diff * looptick.NS2MS:.6f} ms") time.sleep(0.01)
使用上下文方式
from looptick import LoopTick import time with LoopTick() as looptick: for i in range(5): diff = looptick.tick() print(f"第 {i} 次循环耗时: {diff * looptick.NS2MS:.6f} ms") time.sleep(0.01)
输出结果示例:
(LoopTick) PS C:\IT\LoopTick> & C:\IT\LoopTick\.venv\Scripts\python.exe c:/IT/LoopTick/examples/with_usage.py 第 0 次循环耗时: 0.000000 ms 第 1 次循环耗时: 10.829900 ms 第 2 次循环耗时: 16.055800 ms 第 3 次循环耗时: 14.013400 ms 第 4 次循环耗时: 15.587100 ms 总耗时: 0.056486 秒 平均耗时: 14.121550 ms
from looptick import LoopTick import time def stage1(): time.sleep(0.02) # 模拟 I/O 操作 def stage2(): time.sleep(0.05) # 模拟复杂计算 def stage3(): time.sleep(0.01) # 模拟轻量处理 # 使用多阶段测量 linetick = LoopTick() for i in range(3): # 模拟 1000 次循环 start = linetick.tick() # 第一次调用, 返回一个极小值 (0.001) # 完成一次循环后, 返回上一次循环的 mid2 -> start 的用时 # 一般我们不关心 start 变量的值, 仅表示测量开始 stage1() stage2() mid1 = linetick.tick() # 返回 start —> mid1 的用时 stage3() mid2 = linetick.tick() # 返回 mid1 —> mid2 的用时 print(f"\n第 {i} 次循环") print(f"stage1() + stage2() 耗时: {mid1 * linetick.NS2MS:.2f} ms") print(f"stage3() 耗时: {mid2 * linetick.NS2MS:.2f} ms") print(f"本循环总耗时: {(mid1 + mid2) * linetick.NS2MS:.2f} ms") print(f"\n循环任务总耗时: {linetick.total_sec:.6f} 秒")
输出结果示例:
(LoopTick) PS C:\IT\LoopTick> & C:\IT\LoopTick\.venv\Scripts\python.exe c:/IT/LoopTick/examples/lines_usage.py 第 0 次循环 stage1() + stage2() 耗时: 93.78 ms stage3() 耗时: 15.10 ms 本循环总耗时: 108.88 ms 第 1 次循环 stage1() + stage2() 耗时: 89.86 ms stage3() 耗时: 15.11 ms 本循环总耗时: 104.97 ms 第 2 次循环 stage1() + stage2() 耗时: 89.71 ms stage3() 耗时: 15.01 ms 本循环总耗时: 104.72 ms 循环任务总耗时: 0.319596 秒
from looptick import LoopTick import time def stage1(): time.sleep(0.02) # 模拟 I/O 操作 def stage2(): time.sleep(0.05) # 模拟复杂计算 def stage3(): time.sleep(0.03) # 模拟轻量处理 # 使用多阶段测量 + 单独循环测量 linetick = LoopTick() looptick = LoopTick() for i in range(3): # 模拟 1000 次循环 loop_ns = looptick.tick() # 单独使用一个对象测量循环时间 start = linetick.tick() # 第一次调用, 返回一个极小值 (0.001) # 完成一次循环后, 返回上一次循环的 mid2 -> start 的用时 # 一般我们不关心 start 变量的值, 仅表示测量开始 stage1() stage2() mid1 = linetick.tick() # 返回 start —> mid1 的用时 stage3() mid2 = linetick.tick() # 返回 mid1 —> mid2 的用时 print(f"\n第 {i} 次循环") print(f"stage1() + stage2() 耗时: {mid1 * linetick.NS2MS:.2f} ms") print(f"stage3() 耗时: {mid2 * linetick.NS2MS:.2f} ms") print(f"linetick 对象测量的循环耗时: {(mid1 + mid2) * linetick.NS2MS:.2f} ms") print(f"looptick 对象测量的循环耗时: {loop_ns * linetick.NS2MS:.2f} ms") print(f"\nlinetick 对象测量的循环任务总耗时: {linetick.total_sec:.6f} 秒") print(f"looptick 对象测量的循环任务总耗时: {looptick.total_sec:.6f} 秒") print(f"looptick 测量的每次循环平均耗时: {looptick.average_ms:.6f} ms") # 注意: looptick 对象会少一次循环的计时, 因为在第一次调用时只能返回一个极小值 (0.001)
输出结果示例:
(LoopTick) PS C:\IT\LoopTick> & C:\IT\LoopTick\.venv\Scripts\python.exe c:/IT/LoopTick/examples/multi_tick_usage.py 第 0 次循环 stage1() + stage2() 耗时: 94.48 ms stage3() 耗时: 45.04 ms linetick 对象测量的循环耗时: 139.52 ms looptick 对象测量的循环耗时: 0.00 ms 第 1 次循环 stage1() + stage2() 耗时: 89.68 ms stage3() 耗时: 44.96 ms linetick 对象测量的循环耗时: 134.65 ms looptick 对象测量的循环耗时: 140.12 ms 第 2 次循环 stage1() + stage2() 耗时: 89.43 ms stage3() 耗时: 44.88 ms linetick 对象测量的循环耗时: 134.31 ms looptick 对象测量的循环耗时: 135.23 ms linetick 对象测量的循环任务总耗时: 0.409659 秒 looptick 对象测量的循环任务总耗时: 0.275349 秒 looptick 测量的每次循环平均耗时: 137.674650 ms
tick()在第一次调用时, 并没有 "上一次调用", 所以不能获取真正的用时, 默认返回一个极小值 (0.001, 可手动设置),hz与avg_hz数据默认返回 1 Hz