统计Python脚本执行时的CPU使用率,不只是为了知道程序运行了多久,而是要区分进程真正占用处理器的时间与等待IO、休眠等墙钟时间。墙钟时间用time.time()即可获得,但CPU使用率需要采集进程的用户态和内核态CPU时间,再除以实际经过的墙钟时间,或通过操作系统接口读取瞬时占用百分比。Python标准库和第三方库提供了多种实现方式,接下来分别说明。

一、用time模块区分墙钟时间与CPU时间
Python内置的time模块提供了多个计时函数,其中time.time()返回的是系统墙钟时间,也就是从某个固定时刻到现在的秒数,它会受到系统时间调整和休眠的影响。而time.process_time()返回当前进程消耗的CPU时间,只包含用户态和内核态的执行时间,不包括睡眠等待时间。如果脚本中调用了time.sleep()或者等待网络响应,time.process_time()不会把这些等待计入。
对于CPU密集型脚本,用time.process_time()计算出的时间最接近实际处理器占用。下面的示例同时记录墙钟时间和进程CPU时间,可以直观看到二者差异。需要注意的是,time.process_time()在Windows和Linux上都可用,但不同平台对内核态时间的计入策略可能略有不同,不过它已经是标准库中最简单且跨平台的CPU计时方式。
import time
start_wall = time.time()
start_cpu = time.process_time()
total = 0
for i in range(2000000):
total += i
end_cpu = time.process_time()
end_wall = time.time()
print(f"墙钟时间:{end_wall - start_wall:.4f} 秒")
print(f"进程CPU时间:{end_cpu - start_cpu:.4f} 秒")
如果脚本里存在大量IO等待,墙钟时间会明显大于CPU时间,此时不能简单用墙钟时间除以CPU时间得出结论说CPU利用率低,因为IO等待期间处理器可能被其他进程使用。反之,如果墙钟时间与CPU时间非常接近,说明脚本几乎一直在占用处理器,CPU使用率接近100%。这种基础计时方式适合快速判断脚本属于CPU密集型还是IO密集型。
二、通过resource模块读取进程级CPU消耗
如果不仅需要总CPU时间,还想区分用户态和内核态消耗,可以使用标准库中的resource模块。该模块在Unix/Linux平台支持较好,Windows上功能不完整。通过resource.getrusage(resource.RUSAGE_SELF)可以获取当前进程的资源使用数据,其中ru_utime表示用户态CPU时间,ru_stime表示内核态CPU时间,两者单位都是秒。
在任务开始前和结束后分别调用一次getrusage,用差值就能得到该任务消耗的CPU秒数。这个方式比time.process_time()更底层一些,能分别观察用户态和内核态耗时,对排查系统调用频繁、文件读写开销大的脚本很有帮助。ru_maxrss还可以返回最大常驻内存,适合作为辅助指标。
import resource
start = resource.getrusage(resource.RUSAGE_SELF)
total = 0
for i in range(2000000):
total += i
end = resource.getrusage(resource.RUSAGE_SELF)
user_cpu = end.ru_utime - start.ru_utime
sys_cpu = end.ru_stime - start.ru_stime
cpu_seconds = user_cpu + sys_cpu
print(f"用户态CPU时间:{user_cpu:.4f} 秒")
print(f"内核态CPU时间:{sys_cpu:.4f} 秒")
print(f"总CPU时间:{cpu_seconds:.4f} 秒")
如果想要得到这段时间内的平均CPU使用率,可以用总CPU时间除以墙钟时间和CPU核心数。不过对于多线程程序,getrusage返回的是整个进程的CPU时间,可能超过墙钟时间,因为多个线程可以同时在不同核心上执行。此时平均CPU使用率可能超过100%,这是正常现象。
三、用psutil动态监控CPU占用率
resource模块拿到的是累计CPU时间,要转换成百分比还需要额外计算。第三方库psutil则直接提供了cpu_percent()方法,可以返回进程的CPU占用率。安装psutil后,通过psutil.Process(os.getpid())获取当前进程对象,再调用cpu_percent(interval=1)会在1秒间隔内采样并返回平均占用百分比。
psutil的优点是跨平台、接口简单,而且支持对系统整体CPU使用率的监控。例如psutil.cpu_percent(interval=1)可以返回整机平均CPU占用率,psutil.Process.cpu_percent()则只统计单个进程。监控短时任务时需要特别注意,进程可能很快结束,采样间隔太短会导致结果波动较大,通常建议用interval为1秒或更长的时间窗口。
import psutil
import os
import time
p = psutil.Process(os.getpid())
# 先调用一次,初始化内部计数器
p.cpu_percent(interval=None)
time.sleep(0.5)
print(f"当前进程CPU占用率:{p.cpu_percent(interval=None):.1f}%")
print(f"间隔1秒后的进程CPU占用率:{p.cpu_percent(interval=1):.1f}%")
psutil内部同样基于操作系统提供的进程统计信息,例如Linux下读取/proc文件系统,macOS使用libproc,Windows使用Performance Data Helper。因此它的准确性依赖操作系统采样机制,对于毫秒级任务可能出现误差。如果你的脚本运行时间非常短,更建议使用time或resource统计累计CPU时间,而不是依赖百分比采样。
四、函数级CPU热点分析:cProfile与pstats
统计整个脚本的CPU使用率只能看到总体情况,如果想定位到底是哪个函数消耗了最多CPU时间,就需要使用性能分析工具。Python内置的cProfile模块可以记录每个函数的调用次数、总耗时和累计耗时,pstats模块则用于排序和展示分析结果。
与前面几种方式不同,cProfile关注的是函数调用层面的时间分布,它默认记录墙钟时间,但也可以通过自定义计时器记录CPU时间。由于cProfile本身会带来一定开销,通常用于分析运行时间较长的程序。下面的示例创建一个Profile对象,启用分析后执行计算函数,最后输出累计耗时最高的前10个函数。
import cProfile
import pstats
def compute():
total = 0
for i in range(1000000):
total += i ** 2
return total
profiler = cProfile.Profile()
profiler.enable()
compute()
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(10)
在分析结果中,cumtime表示函数及其调用的子函数总共消耗的时间,tottime表示函数自身执行的时间,不包含子函数调用。通过对比这两个字段,可以快速判断热点是在当前函数内部,还是在其调用的某个子函数中。对于CPU密集型的数学计算脚本,这种方法通常能直接定位到最耗时的循环或算法。
五、多线程与多进程场景的注意事项
多线程Python脚本受全局解释器锁影响,CPU密集型任务很难利用多核,因此进程CPU时间可能不会明显高于单线程。此时统计CPU使用率时,如果只用进程级累计时间,会发现它始终接近一个核心的占用。若想观察线程级行为,可以结合操作系统的线程统计工具,或者使用psutil查看进程下的线程信息。
多进程脚本则不同,每个子进程都有自己的CPU时间统计。父进程通过multiprocessing创建子进程后,单独监控父进程无法覆盖子进程的CPU消耗。可以使用psutil遍历所有子进程,或者让每个子进程在退出前将自身CPU时间写入队列,由父进程汇总。整机CPU使用率则可以通过psutil.cpu_percent(interval=1)直接获得,它反映的是所有进程共同占用的处理器资源。
实际项目中,建议先用time或resource做轻量级统计,确认脚本大致耗时和CPU特征,再根据需求引入psutil做动态监控或cProfile做热点分析。短时任务优先累计CPU时间,长时服务优先百分比采样和趋势监控。这样既能避免过度分析,也能得到有参考价值的CPU使用率数据。
Python CPU使用率psutil脚本性能统计修改时间:2026-08-20 22:43:23