Python: 对程序做性能分析及计时统计
1.对整个程序的性能分析
如果只是想简单地对整个程序做计算统计,通常使用UNIX下的time命令就足够了。
(base) ➜ Learn-Python time python someprogram.py
python someprogram.py 0.10s user 0.01s system 98% cpu 0.117 total
由于我用的是Mac系统,和Linux系统的输出可能有不同,不过关键都是这三个时间:
user: 运行用户态代码所花费的时间,也即CPU实际用于执行该进程的时间,其他进程和进程阻塞的时间不计入此数字。
system: 在内核中执行系统调用(如I/O调用)所花费的CPU时间。
total(Linux下应该是real):即挂钟时间(wall-clock time),也称响应时间(response time)、消逝时间(elapsed time),是进程运行开始到结束所有经过的时间,包括了进程使用的时间片和进程阻塞的时间(例如等待I/O完成)。
请注意,若user + system > total,可能存在多个处理器并行工作;
若user + system < total,则可能在等待磁盘、网络或其它设备的响应。
也就说上面这个程序的挂钟时间为0.251s,CPU实际用于执行该进程的时间为0.24s,用于系统调用的时间为0.01s。
再来看看另外一个极端,如果想针对程序的行为产生一份详细的报告,那么可以使用cProfile模块:
(base) ➜ Learn-Python python -m cProfile someprogram.py
7 function calls in 0.071 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.002 0.002 0.071 0.071 someprogram.py:1(<module>)
1 0.039 0.039 0.068 0.068 someprogram.py:1(func1)
1 0.029 0.029 0.029 0.029 someprogram.py:3(<listcomp>)
1 0.000 0.000 0.001 0.001 someprogram.py:7(func2)
1 0.000 0.000 0.000 0.000 someprogram.py:9(<listcomp>)
1 0.000 0.000 0.071 0.071 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
可见我们上述代码的热点是在于func1函数。
这里再多说几句,这里传入的-m -cProfile可选参数意为将Python的cPofile模块做为脚本运行,实际上等价于:
python /Users/orion-orion/miniforge3/lib/python3.9/cProfile.py someprogram.py
当然,中间那个路径取决于大家各自的环境。这也就是说我们将some_program.py做为cProfile.py程序的输入参数,目的就是对其进行性能分析。
2.对特定代码段做性能分析
2.1 分析函数和语句块
不过对于做代码性能分析而言,更常见的情况则处于上述两个极端情况之间。
比如,我们可能已经知道了代码把大部分运行时间都花在几个某几个函数上了。要对函数进行性能分析,使用装饰器就能办到。示例如下:
import time
from functools import wraps
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
r = func(*args, **kwargs)
end = time.perf_counter()
print("{}.{} : {}".format(func.__module__, func.__name__, end - start))
return r
return wrapper
要使用这个装饰器,只要简单地将其放在函数定义之前,就能得到对应函数的计时信息了。示例如下:
@timethis
def countdown(n):
while n > 0:
n -= 1
countdown(10000000)
控制台打印输出:
__main__.countdown : 0.574160792
请注意,在进行性能统计时,任何得到的结果都是近似值。我们这里使用的函数time.perf_counter()是能够提供给定平台上精度最高的计时器,它返回一个秒级的时间值。但是,它计算的仍然是挂钟时间(墙上时间),这会受到许多不同因素的影响(例如机器当前的负载),且它会将程序等待中断的sleep(休眠)时间也计算在内。
如果相对于挂钟时间,我们更感兴趣的是进程时间(包括在内核态和用户态中所花费的CPU时间),那么可以使用time.process_time()来替代。示例如下:
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.process_time()
r = func(*args, **kwargs)
end = time.process_time()
print("{}.{} : {}".format(func.__module__, func.__name__, end - start))
return r
return wrapper
接下来我们看如何对语句块进行计算统计,这可以通过定义一个上下文管理器来实现。示例如下:
from contextlib import contextmanager
@contextmanager
def timeblock(label):
start = time.perf_counter()
try:
yield
finally:
end = time.perf_counter()
print("{} : {}". format(label, end - start))
下面这个例子演示了这个上下文管理器是如何工作的:
with timeblock("counting"):
n = 10000000
while n > 0:
n -= 1
控制台打印输出如下所示:
counting : 0.7888195419999999
最后,我们来看一种一劳永逸的方案:在time模块中的函数之上构建一个更高层的接口来模拟秒表,从而解决对函数、对代码块的计时问题。
import time
class Timer:
def __init__(self, func=time.perf_counter):
self.elapsed = 0.0
self._func = func
self._start = None
def start(self):
if self._start is not None:
raise RuntimeError("Already started!")
self._start = self._func()
def stop(self):
if self._start is None:
raise RuntimeError("Not started!")
end = self._func()
self.elapsed += end - self._start
self._start = None
def reset(self):
self.elapsed = 0.0
@property
def running(self):
return self._start is not None
def __enter__(self):
self.start()
return self
def __exit__(self, *args):
self.stop()
这个类定义了一个定时器,可以根据用户的需要启动、停止和重置它。Timer类将总的花费时间记录在elapsed属性中。下面的实例展示了如何使用这个类:
t = Timer()
# Use 1: Explicit start/stop
t.start()
countdown(1000000)
t.stop()
print(t.elapsed)
# 0.058305625
# Use 2: As a context manager
with t:
countdown(1000000)
print(t.elapsed)
# 0.11482683300000004
with Timer() as t2:
countdown(1000000)
print(t2.elapsed)
# 0.056095916999999995
如同前面所展示的,由Timer类记录的时间是挂钟时间,其中包含了所有的sleeping时间。如果仅想获取进程的CPU时间(包括在用户态和内核态中的时间),可以用time.process_time()取代。示例如下:
t = Timer(time.process_time)
with t:
countdown(1000000)
print(t.elapsed)
# 0.05993699999999999
2.2 分析单条代码片段
如果要对短小的代码片段做性能统计,timeit模块会很有帮助。示例如下:
from timeit import timeit
print(timeit("math.sqrt(2)", "import math"))
# 0.07840395799999997
print(timeit("sqrt(2)", "from math import sqrt"))
# 0.05943025000000002
timeit会执行第一个参数中指定的语句一百万次,然后计算时间。第二个参数是一个配置字符串,在运行测试之前会先执行以设定好环境。如果要修改需要迭代的次数,只需要提供一个number参数即可:
print(timeit("math.sqrt(2)", "import math", number=10000000))
# 0.7569702089999999
print(timeit("sqrt(2)", "from math import sqrt", number=10000000))
# 0.5865757500000002
最后但同样重要的是,如果打算进行详细的计时统计分析,请确保先阅读time、timeit以及其他相关模块的文档。这样才能理解不同系统平台之间的重要差异以及其他一些缺陷。
参考
- [1] https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1
- [2] https://stackoverflow.com/questions/7335920/what-specifically-are-wall-clock-time-user-cpu-time-and-system-cpu-time-in-uni
- [3] https://stackoverflow.com/questions/22241420/execution-of-python-code-with-m-option-or-not
- [4] https://stackoverflow.com/questions/66036844/time-time-or-time-perf-counter-which-is-faster
- [5] Martelli A, Ravenscroft A, Ascher D. Python cookbook[M]. " O'Reilly Media, Inc.", 2015.
Python: 对程序做性能分析及计时统计的更多相关文章
- Python:用pyinstrument做性能分析
导引 在计算密集型计算或一些Web应用中,我们常常需要对代码做性能分析.在Python中,最原始的方法即是使用time包中的time函数(该函数以秒为计时单位): from time import s ...
- chrome使用Timeline做性能分析
使用Timeline做性能分析 Timeline面板记录和分析了web应用运行时的所有活动情况,这是研究和查找性能问题的最佳途径.###Timeline面板概览 Timeline面板主要有三个部分构成 ...
- Python 优化第一步: 性能分析实践 使用cporfile+gprof2dot可视化
拿来主义: python -m cProfile -o profile.pstats to_profile.py gprof2dot -f pstats profile.pstats |dot -Tp ...
- 软件性能测试分析与调优实践之路-Java应用程序的性能分析与调优-手稿节选
Java编程语言自从诞生起,就成为了一门非常流行的编程语言,覆盖了互联网.安卓应用.后端应用.大数据等很多技术领域,因此Java应用程序的性能分析和调优也是一门非常重要的课题.Java应用程序的性能直 ...
- Python程序的性能分析指南(转)
原文地址 :http://blog.jobbole.com/47619/ 虽然不是所有的Python程序都需要严格的性能分析,不过知道如何利用Python生态圈里的工具来分析性能,也是不错的. 分析一 ...
- python面试总结3(性能分析优化,GIl常考题)
python性能分析和优化,GIL常考题 什么是Cpython GIL Cpython解释器的内存管理并不是线程安全的 保护多线程情况下对python对象访问 Cpython使用简单的锁机制避免多个线 ...
- golang 使用pprof和go-torch做性能分析
软件开发过程中,项目上线并不是终点.上线后,还要对程序的取样分析运行情况,并重构现有的功能,让程序执行更高效更稳写. golang的工具包内自带pprof功能,使找出程序中占内存和CPU较多的部分功能 ...
- python 数据较大 性能分析
前提:若有一个几百M的文件需要解析,某个函数需要运行很多次(几千次),需要考虑性能问题 性能分析模块:cProfile 使用方法:cProfile.run("func()"),其中 ...
- 使用JDK自带的VisualVM进行Java程序的性能分析
VisualVM是什么? VisualVM是JDK自带的一个用于Java程序性能分析的工具,JDK安装完毕后就有啦,在JDK安装目录的bin文件夹下能找到名称为jvisualvm.exe. 要使用Vi ...
- 使用xdebug对php做性能分析调优
作为PHP程序员我们或多或少都了解或使用过xdebug.此文章记录安装和配置xdebug,以及如何使用它来分析php程序. 我的机器环境: mac, php 安装 xdebug 推荐使用 pecl 安 ...
随机推荐
- AD画板从头开始
AD画板从头开始 前言 近期认真的画了一次板子,以前虽然也画过,但是都是很随意的,这次是做一个小项目,然后因为有一段时间没有画板了,发现自己很多基础的东西都忘记了,这里就来记录一下从头到尾的过程.本次 ...
- ConcurrentDictionary<T,V> 的这两个操作不是原子性的
好久不见,马甲哥封闭居家半个月,记录之前遇到的一件小事. ConcurrentDictionary<TKey,TValue>绝大部分api都是线程安全且原子性的, 唯二的例外是接收工厂委托 ...
- 云服务器 Centos7 部署 Elasticsearch 8.0 + Kibana 8.0 指南
文章转载自:https://mp.weixin.qq.com/s/iPfh9Mkwxf5lieiqt6ltxQ 服务器是命令行模式登录,没法以浏览器方式访问.而官方推荐的快捷部署方式,在kibana ...
- 使用pip的方式安装docker-compose
# 国内开启pip 下载加速:http://mirrors.aliyun.com/help/pypi mkdir ~/.pip/ cat > ~/.pip/pip.conf <<'E ...
- 2022美团Java后端开发春招实习面经
2022美团Java后端开发春招实习面经 一面 1.讲一下计算机网络的五层架构,每层分别有什么协议 五层架构:应用层.运输层.网络层.数据链路层.物理层 2.什么是 Http 协议,各种 Ht ...
- Android类加载流程
背景 由于前前前阵子写了个壳,得去了解类的加载流程,当时记了一些潦草的笔记.这几天把这些东西简单梳理了一下,本文分析的代码基于Android8.1.0源码. 流程分析 从loadClass开始,我们来 ...
- 洛谷P4630 [APIO2018] Duathlon 铁人两项 (圆方树)
圆方树大致理解:将每个点双看做一个新建的点(方点),该点双内的所有点(圆点)都向新建的点连边,最后形成一棵树,可以给点赋予点权,用以解决相关路径问题. 在本题中,方点点权赋值为该点双的大小,因为两个点 ...
- day08-MySQL事务
MySQL事务 先来看一个例子 有一张balance表: 需求:将tom的100块钱转到King账户中 执行的操作是: update balance set money = money -100 wh ...
- SSM框架整合图书管理项目
SSM框架整合 1.建立简单的maven项目 2.导入依赖 <?xml version="1.0" encoding="UTF-8"?> <p ...
- echarts在Vue项目中的实际运用效果图
文章目录 1.在后台系统首页中.可以根据需求制作相应的图表 2.在Vue中使用echarts的详细过程参照这个链接 1.在后台系统首页中.可以根据需求制作相应的图表 2.在Vue中使用echarts的 ...