Python代码性能评估实战:time模块核心时钟源与计时策略详解
1. 项目概述为什么我们需要精确评估Python代码运行时间在Python开发的日常工作中无论是优化一个核心算法还是排查一个线上服务的性能瓶颈亦或是仅仅想比较两种实现方式的效率差异一个最基础、最直接的需求就是精确地知道一段代码到底跑了多久。这个看似简单的需求背后却藏着不少门道。你可能会说这不就是掐个表吗但在程序的世界里尤其是在Python这样解释型、带全局解释器锁GIL的语言里“掐表”本身就有好几种“表”每种表测量的“时间”含义都不同。这就是time模块的价值所在。它不是一个复杂的性能剖析器Profiler而是一把精准的“秒表”和“挂钟”。对于开发者而言掌握time模块的核心函数意味着你拥有了量化代码性能的第一手工具。从简单的脚本计时到复杂的性能监控埋点都离不开它。尤其当我们看到热搜词里频繁出现“python读取excel数据全部读取耗时5分钟仅读几列也是5分钟怎么回事”这类具体性能问题时精准的计时是定位问题的第一步。你不能靠感觉说“好像很慢”你得拿出数据“函数A耗时2.8秒其中95%的时间花在了IO等待上”。本文将深入拆解Python标准库中的time模块聚焦于其用于评估运行时间的核心功能。我不会停留在简单的time.time()用法而是会带你理解不同时间函数背后的时钟源差异探讨在单线程、多线程、IO密集型、CPU密集型等不同场景下如何选择最合适的计时方法。同时我会分享大量从实际项目调试中总结出的“避坑指南”和操作技巧让你不仅能测出时间更能测准时间读懂时间数据背后的故事。2.time模块的核心时钟源你的“秒表”准吗在开始写第一行计时代码前我们必须搞清楚一个根本问题time模块提供的几种时间到底是从哪里来的它们测量的是什么理解这一点是避免误用和错误解读数据的关键。2.1 墙上时钟时间Wall-clock Timetime.time()这是最直观、最常用的时间。time.time()返回的是自纪元Epoch通常是1970年1月1日00:00:00 UTC以来的秒数这是一个浮点数。它反映的是现实世界中流逝的时间就像你看着墙上的钟表一样。核心原理与特点来源通常来自操作系统的系统时钟。这个时钟可能会被NTP网络时间协议同步也可能会被用户手动调整。因此它不是一个单调递增的时钟。如果在计时过程中发生了系统时间调整向前或向后那么用time.time()计算出的差值就会失真。精度在现代系统上通常能达到微秒microsecond级别但具体精度取决于操作系统和硬件。适用场景测量一段代码运行所经历的真实世界时间特别是涉及网络请求、磁盘IO、用户交互等需要等待外部事件的任务。对于大多数需要报告“总共花了多久”的日常脚本和功能这是首选。实操示例与陷阱import time start time.time() # 模拟一个耗时操作比如请求一个网页 time.sleep(2.5) # 休眠2.5秒 end time.time() duration end - start print(f任务耗时: {duration:.4f} 秒) # 输出应接近 2.5000注意在虚拟化环境如Docker容器或负载极高的服务器上time.sleep()的精度可能会下降因为CPU时间片可能被剥夺。此时duration可能略大于2.5秒。2.2 单调时钟时间Monotonic Timetime.monotonic()为了解决系统时间可能被调整导致计时失真的问题Python 3.3引入了time.monotonic()。它返回一个单调递增的时钟值其零点未定义只保证差值有意义。核心原理与特点来源来自一个不会被系统时间调整影响的时钟源如系统启动后的计时器。它保证时间值永远不会往回跳。精度通常高于或等于time.time()并且提供纳秒级精度的time.monotonic_ns()Python 3.7。适用场景任何需要高可靠性、长时间运行的计时任务。例如性能基准测试、计算超时、游戏循环、动画帧率计算等。只要你想测量时间间隔并且不想被系统时间更改干扰就用它。实操心得import time start time.monotonic() # 执行一些不确定的操作可能涉及系统调用 result some_io_heavy_function() end time.monotonic() elapsed end - start print(f操作耗时: {elapsed:.6f} 秒)一个关键技巧在编写需要长时间运行如数小时或数天的监控或调度脚本时务必使用time.monotonic()来计算间隔和判断超时。我曾经在一个后台数据处理服务中因为使用time.time()计算任务间隔结果在服务器自动同步NTP时间后逻辑判断出现混乱导致任务重复执行。改用monotonic()后问题彻底解决。2.3 进程时间Process Timetime.process_time()这个时间测量的是当前进程在CPU上执行用户态和内核态代码所花费的时间。简单说就是CPU真正为你这个进程干活的时间。当你的进程被操作系统挂起例如在等待IO或者因为时间片用完让给其他进程这个时钟是不走的。核心原理与特点来源操作系统提供的进程级CPU时间计数器。含义它衡量的是CPU的“工作量”而不是墙上时钟的“流逝量”。这对于评估算法的纯计算效率至关重要。适用场景评估CPU密集型任务的性能比较不同算法的计算效率。例如对比两种排序算法、矩阵运算的实现等。如果你想回答“这个函数计算部分到底有多快”而不是“它从开始到结束等了多久”就用这个。实操示例与对比import time def cpu_intensive_task(n): sum 0 for i in range(n): sum i * i return sum # 使用墙上时钟时间 wall_start time.time() cpu_start time.process_time() result cpu_intensive_task(10_000_000) cpu_end time.process_time() wall_end time.time() print(f墙上时钟耗时: {wall_end - wall_start:.4f} 秒) print(f进程CPU耗时: {cpu_end - cpu_start:.4f} 秒)运行这段代码你可能会发现“进程CPU耗时”显著小于“墙上时钟耗时”。这是因为Python循环本身有开销且操作系统调度也会引入额外时间。这个差值墙上时间 - CPU时间大致反映了进程等待、IO虽然这里没有等非计算开销。一个常见误区对于IO密集型任务如网络下载、文件读写process_time()会非常短因为大部分时间进程都在休眠等待CPU没在干活。此时用它来评估“性能”是无效的应该用time.time()或time.monotonic()。2.4 线程时间Thread Timetime.thread_time()(Python 3.7)这是process_time()的更细粒度版本它测量的是当前线程的CPU时间。在多线程程序中所有线程共享进程的CPU时间。thread_time()可以帮助你分析每个线程具体的计算负载。核心原理与特点来源操作系统提供的线程级CPU时间计数器如果系统支持。适用场景多线程程序性能剖析。用于分析哪个线程是计算热点或者检查多线程负载是否均衡。使用注意由于GIL的存在Python的多线程并不能实现真正的并行计算对于CPU密集型任务。因此在CPU密集型多线程场景下所有线程的thread_time之和通常会接近或略大于总的process_time因为包含线程切换开销。但对于IO密集型多线程thread_time依然能准确反映每个线程在等待IO间隙所做的少量计算工作。3. 实战构建一个健壮且高精度的计时装饰器理解了核心时钟源我们就可以动手打造一个在项目中通用的计时工具了。直接在每个函数前后写start/end变量既重复又容易出错。装饰器Decorator是解决这个问题的完美方案。3.1 基础版计时装饰器我们先实现一个最通用的版本使用time.monotonic()保证可靠性。import time import functools from typing import Callable, Any def timer(func: Callable) - Callable: 一个简单的计时装饰器打印函数执行时间。 使用单调时钟避免系统时间调整的影响。 functools.wraps(func) # 保留原函数的元信息如名字、文档字符串 def wrapper(*args, **kwargs) - Any: start_time time.monotonic() result func(*args, **kwargs) # 执行被装饰的函数 end_time time.monotonic() elapsed end_time - start_time print(f[timer] 函数 {func.__name__} 执行耗时: {elapsed:.6f} 秒) return result return wrapper # 使用示例 timer def example_function(n): 一个示例函数计算平方和。 s sum(i*i for i in range(n)) return s if __name__ __main__: example_function(1000000)这个装饰器简单有效但它把输出直接打印到控制台不便于在复杂程序中收集和汇总数据。3.2 进阶版可配置、可收集数据的计时器在实际项目中我们可能需要选择不同的时钟源monotonic,process_time等。将计时结果收集起来用于后续生成报告或日志。控制是否输出。import time import functools from enum import Enum from typing import Callable, Any, Optional, Dict class ClockType(Enum): 时钟类型枚举 WALL time.time # 墙上时钟 MONOTONIC time.monotonic # 单调时钟 PROCESS time.process_time # 进程CPU时间 THREAD time.thread_time # 线程CPU时间 (Python 3.7) # 用于全局收集计时数据的简单存储 timing_data: Dict[str, list] {} def advanced_timer( clock: ClockType ClockType.MONOTONIC, store_key: Optional[str] None, verbose: bool True ): 高级计时装饰器。 参数: clock: 使用的时钟类型默认为单调时钟。 store_key: 如果提供则将耗时秒追加到全局字典 timing_data[store_key] 中。 verbose: 是否打印耗时信息。 def decorator(func: Callable) - Callable: functools.wraps(func) def wrapper(*args, **kwargs) - Any: start clock.value() result func(*args, **kwargs) end clock.value() elapsed end - start if store_key: timing_data.setdefault(store_key, []).append(elapsed) if verbose: clock_name clock.name print(f[timer::{clock_name}] {func.__name__} - {elapsed:.6f}s) return result return wrapper return decorator # 使用示例 advanced_timer(clockClockType.PROCESS, store_keycalc_squares, verboseTrue) def calculate_squares(n): return [i*i for i in range(n)] advanced_timer(clockClockType.WALL, store_keynetwork_op, verboseFalse) def simulated_network_request(): time.sleep(0.5) # 模拟网络延迟 return response if __name__ __main__: # 执行函数 calculate_squares(50000) simulated_network_request() simulated_network_request() # 查看收集的数据 print(\n收集的计时数据:) for key, values in timing_data.items(): if values: avg sum(values) / len(values) print(f {key}: 调用次数{len(values)}, 平均耗时{avg:.6f}s, 详情{values})实操心得与避坑指南装饰器副作用装饰器会改变原函数的__name__等属性。务必使用functools.wraps(func)来保留它们这对于调试和序列化非常重要。时钟选择对于包含time.sleep()或任何IO操作的函数不要使用ClockType.PROCESS或ClockType.THREAD因为睡眠和等待期间CPU时间为0你会得到一个近乎0的耗时这完全不能反映真实情况。对于混合型任务既有计算又有IO如果想了解计算部分占比可以同时用WALL和PROCESS计时。最小化测量开销计时操作本身调用time.monotonic()也有极短耗时纳秒级。对于执行时间极短的函数如微秒级多次测量取平均值更为可靠。装饰器本身也会引入一点点函数调用开销在测量极短函数时需要意识到这一点。3.3 使用上下文管理器进行代码块计时装饰器适合给整个函数计时。但有时我们只想测量一段代码块比如循环体内部、某个算法步骤。这时上下文管理器Context Manager是更好的选择。import time from contextlib import contextmanager from typing import Generator contextmanager def time_block(description: str 代码块, clocktime.monotonic): 用于测量代码块执行时间的上下文管理器。 示例: with time_block(数据库查询): data db.query(...) start clock() try: yield # 在此处执行被测量的代码块 finally: end clock() elapsed end - start print(f[time_block] {description} 耗时: {elapsed:.6f} 秒) # 使用示例 if __name__ __main__: total 0 with time_block(计算密集型循环): for i in range(1000000): total i # 嵌套使用也是安全的 with time_block(外层操作): time.sleep(0.1) with time_block(内层计算): _ sum(range(10000))上下文管理器的优势在于灵活可以精确控制计时的起止范围并且能很好地处理代码块中发生异常的情况try...finally结构确保了即使出错计时也会结束并打印。4. 深入场景分析不同任务类型下的计时策略掌握了工具我们来看看如何将它们应用到热搜词中提到的具体场景。4.1 场景一IO密集型任务分析“python读取excel耗时”问题热搜词中有一个典型问题“python读取excel数据全部读取耗时5分钟仅读几列也是5分钟怎么回事”。这很可能是一个IO瓶颈问题而非计算瓶颈。我们的计时策略需要能区分“等待时间”和“处理时间”。假设分析使用pandas读取Excel。import pandas as pd import time def read_excel_file(file_path, usecolsNone): 读取Excel文件可选择特定列。 # 使用墙上时钟测量总耗时 wall_start time.monotonic() # 使用进程时间测量pandas实际解析数据消耗的CPU时间 cpu_start time.process_time() try: df pd.read_excel(file_path, usecolsusecols) except Exception as e: print(f读取失败: {e}) df None cpu_end time.process_time() wall_end time.monotonic() wall_time wall_end - wall_start cpu_time cpu_end - cpu_start print(f文件: {file_path}) print(f 读取列: {usecols or 全部}) print(f 墙上时钟总耗时: {wall_time:.2f} 秒) print(f 进程CPU耗时: {cpu_time:.2f} 秒) print(f IO等待占比: {(wall_time - cpu_time) / wall_time * 100:.1f}%) return df # 模拟测试需要实际文件路径 # df_all read_excel_file(large_data.xlsx) # df_few read_excel_file(large_data.xlsx, usecols[A, B, C])结果解读与排查思路情况A如果read_excel总耗时墙上时间很长但CPU时间很短且两者差值巨大例如总耗时300秒CPU时间只有2秒。这说明时间主要花在了磁盘IO上。可能的原因有文件巨大、磁盘速度慢如机械硬盘、文件在远程网络驱动器上。情况B如果读取全部列和读取几列的总耗时几乎一样但CPU时间不同读全部列CPU时间更长。这强烈暗示pandas在读取时无论指定多少列都可能需要先解析整个文件的结构或元数据或者文件读取的IO开销是主要部分内存中筛选列的计算开销相对很小。此时优化方向不是减少读取列而是考虑将Excel转换为更高效的格式如Parquet、Feather或HDF5。使用openpyxl引擎并设置read_onlyTrue模式对于.xlsx文件它允许流式读取不一次性加载整个文件到内存。检查是否启用了“公式计算”或链接这会在打开时触发重算极其耗时。4.2 场景二CPU密集型算法优化“线材优化python算法”对于纯计算任务如算法优化、数值计算我们需要最纯净的CPU时间来衡量算法效率排除操作系统调度和其他进程干扰。策略使用time.process_time()这是最准确的。多次运行取平均单次运行可能受到操作系统调度、CPU频率波动睿频/降频、甚至其他进程偶然活动的影响。通过多次运行例如1000次或10000次并取平均值可以获得更稳定、可重复的结果。预热对于涉及Python字节码缓存、JIT编译如PyPy、Numba或库初始化如NumPy的代码第一次运行通常较慢。在正式计时前先运行几次“预热”循环让系统状态稳定。使用timeit模块Python标准库提供了专为小代码片段基准测试设计的timeit模块它自动处理了多次运行、取平均、禁用垃圾回收等细节是微基准测试的黄金标准。示例对比两种求和方法import time import timeit def sum_with_for_loop(n): 使用for循环求和 total 0 for i in range(n): total i return total def sum_with_builtin(n): 使用内置sum和range求和 return sum(range(n)) # 方法1手动使用process_time多次测量 def benchmark_manual(func, n, repeat1000): total_cpu_time 0.0 for _ in range(repeat): start time.process_time() func(n) end time.process_time() total_cpu_time (end - start) avg_time total_cpu_time / repeat return avg_time n 10000 avg_loop benchmark_manual(sum_with_for_loop, n) avg_builtin benchmark_manual(sum_with_builtin, n) print(f手动基准测试 (n{n}, repeat1000):) print(f for循环: {avg_loop:.8f} 秒/次) print(f 内置sum: {avg_builtin:.8f} 秒/次) print(f 内置sum比for循环快 {avg_loop/avg_builtin:.2f} 倍) # 方法2使用timeit模块更推荐 print(f\n使用timeit模块:) stmt_loop sum_with_for_loop(10000) stmt_builtin sum_with_builtin(10000) setup from __main__ import sum_with_for_loop, sum_with_builtin time_loop timeit.timeit(stmt_loop, setupsetup, number1000) time_builtin timeit.timeit(stmt_builtin, setupsetup, number1000) print(f for循环: {time_loop/1000:.8f} 秒/次) print(f 内置sum: {time_builtin/1000:.8f} 秒/次)关键发现你会看到内置sum远远快于手写for循环因为它是用C实现的避免了Python字节码解释的开销。这个例子说明了为什么在性能关键路径上应尽量使用内置函数和标准库中高效的C扩展如NumPy。4.3 场景三多线程/异步程序性能剖析对于并发程序计时变得更加复杂。我们需要关注整体吞吐量、每个任务的延迟以及线程/异步任务的实际工作时间。多线程计时要点整体耗时使用time.monotonic()测量从启动所有线程到所有线程结束的墙上时间。线程工作时间在每个线程的函数内部使用time.thread_time()来测量该线程实际消耗的CPU时间。这有助于识别计算热点线程。注意GIL由于GIL多线程在CPU密集型任务上无法加速甚至可能因锁竞争而变慢。此时各线程的thread_time之和可能接近或超过总的墙上时间。对于IO密集型任务多线程可以有效利用等待时间此时墙上时间会远大于单个线程的CPU时间之和。异步asyncio程序计时要点异步程序的并发是协作式的在单个线程内切换。因此time.process_time()测量的是整个事件循环线程的CPU时间。测量单个async函数的耗时仍然可以在其内部用time.monotonic()因为可能包含awaitIO等待。要测量整个异步任务的吞吐量还是使用墙上时钟time.monotonic()。5. 高级技巧与性能剖析工具入门虽然time模块是基础但在复杂的性能优化工作中我们往往需要更强大的工具。5.1 使用time.perf_counter()进行高精度基准测试time.perf_counter()返回一个具有最高可用分辨率的性能计数器的值用于测量短持续时间。它包括了睡眠期间的时间并且是单调的。它是time.monotonic()的高精度版本通常用于微基准测试。import time # 比较perf_counter和monotonic的精度名义上 start_p time.perf_counter() start_m time.monotonic() # 执行一个非常快速的操作 _ sum(range(1000)) end_p time.perf_counter() end_m time.monotonic() print(fperf_counter 差值: {end_p - start_p:.9f}秒) print(fmonotonic 差值: {end_m - start_m:.9f}秒) # 通常perf_counter能显示出更高的精度更多小数位有效何时使用当你需要测量极短时间间隔纳秒到微秒级例如内层循环、单个函数调用开销时优先使用time.perf_counter()或time.perf_counter_ns()。5.2 结合cProfile进行函数级性能剖析time模块告诉你“哪里花了时间”但cProfile可以告诉你“时间具体花在了哪个函数调用上”。它是Python标准库中的性能剖析器可以统计每个函数的调用次数和耗时。import cProfile import pstats import io from advanced_timer_example import calculate_squares # 引用之前的函数 def profile_example(): pr cProfile.Profile() pr.enable() # 开始收集性能数据 # 运行你想要剖析的代码 for _ in range(100): calculate_squares(5000) pr.disable() # 停止收集 s io.StringIO() # 按累计时间排序只输出前10行 ps pstats.Stats(pr, streams).sort_stats(cumulative) ps.print_stats(10) print(s.getvalue()) if __name__ __main__: profile_example()cProfile的输出会显示一个表格包含ncalls调用次数、tottime该函数本身消耗的总时间不包括子函数、percalltottime除以ncalls、cumtime该函数及其所有子函数消耗的累计时间等列。通过分析cumtime你可以快速找到程序的性能瓶颈函数。5.3 可视化工具将时间数据转化为图表对于长期运行的程序或需要趋势分析的情况可以将time模块收集的数据记录下来并用matplotlib等库进行可视化。import time import random import matplotlib.pyplot as plt # 模拟一个每次调用耗时略有波动的函数 def fluctuating_task(): time.sleep(random.uniform(0.01, 0.05)) # 随机睡眠10-50毫秒 return True # 收集数据 durations [] for i in range(100): start time.monotonic() fluctuating_task() end time.monotonic() durations.append((end - start) * 1000) # 转换为毫秒 # 绘制折线图 plt.figure(figsize(10, 5)) plt.plot(durations, markero, linestyle-, markersize3) plt.title(函数单次执行耗时趋势 (毫秒)) plt.xlabel(执行次数) plt.ylabel(耗时 (ms)) plt.grid(True, alpha0.3) plt.tight_layout() plt.show() # 绘制直方图查看分布 plt.figure(figsize(10, 5)) plt.hist(durations, bins20, edgecolorblack, alpha0.7) plt.title(函数执行耗时分布) plt.xlabel(耗时 (ms)) plt.ylabel(频次) plt.grid(True, alpha0.3, axisy) plt.tight_layout() plt.show()可视化能帮你发现性能的波动模式、异常点如偶尔的卡顿以及耗时分布是否稳定这对于服务端应用的性能监控尤其有用。6. 常见问题排查与实战心得在实际使用time模块进行性能评估时会遇到一些典型问题。这里记录下我踩过的坑和解决方案。6.1 问题计时结果波动巨大每次运行差异很大可能原因与解决方案系统负载波动后台有其他重量级进程如杀毒软件、系统更新在运行。尽量在系统空闲时进行基准测试并关闭不必要的程序。CPU频率缩放Frequency Scaling现代CPU会根据负载动态调整频率以节能。这会导致计算速度波动。对于严肃的基准测试可以在BIOS中关闭节能选项如Intel的SpeedStep, AMD的Cool‘n’Quiet或在操作系统中将CPU电源计划设置为“高性能”。预热不足如之前所述第一次运行Python函数通常较慢由于字节码编译、缓存未命中等。务必进行预热。测量时间过短如果被测代码本身执行时间极短如小于1微秒那么计时函数调用本身的开销、操作系统的计时器精度就会成为主要误差来源。解决方法是测量多次执行的总时间然后计算平均值这正是timeit模块的做法。垃圾回收GC干扰Python的垃圾回收可能在任何时刻发生导致不可预测的停顿。在基准测试中可以使用gc.disable()临时禁用GC但务必在测试后重新启用gc.enable()。timeit模块默认会禁用GC。6.2 问题多进程中如何准确计时time.process_time()在Unix和Windows上都是进程相关的测量的是当前进程的CPU时间。但在使用multiprocessing模块创建子进程时父进程无法直接获取子进程的process_time。解决方案在子进程的目标函数内部自己测量process_time并通过队列Queue、管道Pipe或共享内存将结果传回父进程。使用第三方库如psutil它可以跨平台获取任何指定进程的CPU时间信息。import multiprocessing as mp import time def worker(n, result_queue): 子进程任务 cpu_start time.process_time() # 模拟计算 total sum(i*i for i in range(n)) cpu_end time.process_time() cpu_time cpu_end - cpu_start result_queue.put((total, cpu_time)) if __name__ __main__: result_queue mp.Queue() processes [] wall_start time.monotonic() for i in range(4): # 启动4个子进程 p mp.Process(targetworker, args(5_000_000, result_queue)) processes.append(p) p.start() for p in processes: p.join() wall_end time.monotonic() total_wall_time wall_end - wall_start print(f总墙上时钟时间: {total_wall_time:.2f}秒) # 收集子进程的CPU时间 child_cpu_times [] while not result_queue.empty(): total, cpu_time result_queue.get() child_cpu_times.append(cpu_time) print(f子进程返回结果 {total} CPU耗时: {cpu_time:.2f}秒) print(f子进程CPU时间总和: {sum(child_cpu_times):.2f}秒)注意在多核CPU上子进程是并行执行的因此总的墙上时间可能远小于子进程CPU时间之和如果它们是纯CPU密集型任务。6.3 心得建立性能测试的“实验室环境”为了获得可重复、可比较的性能数据我建议建立一个干净的测试环境固定输入使用相同大小和内容的数据集进行测试。控制环境关闭不必要的应用程序和服务保持测试环境一致。多次运行任何单次测量都不可靠至少运行5-10次去掉最高和最低值取中间值的平均。记录环境信息记录Python版本、操作系统、CPU型号、内存大小等这些都可能影响结果。使用专门的基准测试框架对于大型项目考虑使用pytest-benchmark这样的插件它能自动化上述很多步骤并提供详细的统计报告和对比。6.4 一个真实的排查案例数据库查询忽快忽慢我曾遇到一个Web服务某个API接口的响应时间偶尔会从平均50ms飙升到2s以上。使用time.monotonic()在关键代码段打点点A接收到请求。点B完成数据库连接获取。点C完成SQL查询执行。点D完成结果序列化。通过长期日志分析发现B到C的时间偶尔会异常高。这说明问题出在数据库查询执行阶段而不是网络或代码逻辑。进一步排查发现是数据库的某个表缺少关键索引当查询条件命中特定值时导致了全表扫描。建立索引后问题消失。核心经验分层计时是定位性能问题的利器。不要只记录一个总时间而要在可能出问题的各个阶段网络连接、数据库查询、计算、序列化都加上计时点。当问题出现时通过对比各阶段耗时就能快速将问题范围缩小到某个具体环节。time模块提供的各种时钟就是实现这种分层计时的基础工具。