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

最后但同样重要的是,如果打算进行详细的计时统计分析,请确保先阅读timetimeit以及其他相关模块的文档。这样才能理解不同系统平台之间的重要差异以及其他一些缺陷。

参考

Python: 对程序做性能分析及计时统计的更多相关文章

  1. Python:用pyinstrument做性能分析

    导引 在计算密集型计算或一些Web应用中,我们常常需要对代码做性能分析.在Python中,最原始的方法即是使用time包中的time函数(该函数以秒为计时单位): from time import s ...

  2. chrome使用Timeline做性能分析

    使用Timeline做性能分析 Timeline面板记录和分析了web应用运行时的所有活动情况,这是研究和查找性能问题的最佳途径.###Timeline面板概览 Timeline面板主要有三个部分构成 ...

  3. Python 优化第一步: 性能分析实践 使用cporfile+gprof2dot可视化

    拿来主义: python -m cProfile -o profile.pstats to_profile.py gprof2dot -f pstats profile.pstats |dot -Tp ...

  4. 软件性能测试分析与调优实践之路-Java应用程序的性能分析与调优-手稿节选

    Java编程语言自从诞生起,就成为了一门非常流行的编程语言,覆盖了互联网.安卓应用.后端应用.大数据等很多技术领域,因此Java应用程序的性能分析和调优也是一门非常重要的课题.Java应用程序的性能直 ...

  5. Python程序的性能分析指南(转)

    原文地址 :http://blog.jobbole.com/47619/ 虽然不是所有的Python程序都需要严格的性能分析,不过知道如何利用Python生态圈里的工具来分析性能,也是不错的. 分析一 ...

  6. python面试总结3(性能分析优化,GIl常考题)

    python性能分析和优化,GIL常考题 什么是Cpython GIL Cpython解释器的内存管理并不是线程安全的 保护多线程情况下对python对象访问 Cpython使用简单的锁机制避免多个线 ...

  7. golang 使用pprof和go-torch做性能分析

    软件开发过程中,项目上线并不是终点.上线后,还要对程序的取样分析运行情况,并重构现有的功能,让程序执行更高效更稳写. golang的工具包内自带pprof功能,使找出程序中占内存和CPU较多的部分功能 ...

  8. python 数据较大 性能分析

    前提:若有一个几百M的文件需要解析,某个函数需要运行很多次(几千次),需要考虑性能问题 性能分析模块:cProfile 使用方法:cProfile.run("func()"),其中 ...

  9. 使用JDK自带的VisualVM进行Java程序的性能分析

    VisualVM是什么? VisualVM是JDK自带的一个用于Java程序性能分析的工具,JDK安装完毕后就有啦,在JDK安装目录的bin文件夹下能找到名称为jvisualvm.exe. 要使用Vi ...

  10. 使用xdebug对php做性能分析调优

    作为PHP程序员我们或多或少都了解或使用过xdebug.此文章记录安装和配置xdebug,以及如何使用它来分析php程序. 我的机器环境: mac, php 安装 xdebug 推荐使用 pecl 安 ...

随机推荐

  1. 01 uniapp/微信小程序 项目day01

    一.起步 1.1 配置uni-app开发环境 什么是uni-app,就是基于vue的一个开发框架,可以将我们写的一套代码,同时发布到ios.安卓.小程序等多个平台 官方推荐使用Hbuilderx来写u ...

  2. 普通用户使用CI/CD权限使用

    根据文章:授权用户访问名称空间 (https://www.cnblogs.com/sanduzxcvbnm/p/15015576.html) 进行有关操作后,普通用户点击 会报错如下信息: 解决办法: ...

  3. 洛谷P5788 单调栈(模板)

    1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=3e6+10; 4 int n,a[N],s[N],ans[N ...

  4. bootstrapValidator 参数校验框架

    bootstrap:能够增加兼容性的强大框架. 因为项目需要数据验证,看bootstrapValidator 还不错,就上手一直,完美兼容,话不多说. 需要引用css: bootstrap.min.c ...

  5. CentOS 7.9 安装 kafka_2.13

    一.CentOS 7.9 安装 kafka_2.13 地址 https://kafka.apache.org/downloads.html 二.安装准备 1 安装JDK 在安装kafka之前必须先安装 ...

  6. 记一次 .NET 某企业OA后端服务 卡死分析

    一:背景 1.讲故事 前段时间有位朋友微信找到我,说他生产机器上的 Console 服务看起来像是卡死了,也不生成日志,对方也收不到我的httpclient请求,不知道程序出现什么情况了,特来寻求帮助 ...

  7. 一篇文章带你掌握MyBatis简化框架——MyBatisPlus

    一篇文章带你掌握MyBatis简化框架--MyBatisPlus 我们在前面的文章中已经学习了目前开发所需的主流框架 类似于我们所学习的SpringBoot框架用于简化Spring开发,我们的国人大大 ...

  8. AgileBoot - 项目内统一的错误码设计

    本篇文章主要探讨关于统一错误码的设计,并提供笔者的实现 欢迎大家讨论,指正. 该错误码的设计在仓库: github:https://github.com/valarchie/AgileBoot-Bac ...

  9. .NET Core C#系列之XiaoFeng.Threading.JobScheduler作业调度

    作业调度其实就是一个定时器,定时完成某件事, 比如:每分钟执行一次,每小时执行一次,每天执行一次,第二周几执行,每月几号几点执行,间隔多少个小时执行一次等. 作业类:XiaoFeng.Threadin ...

  10. Windows7下驱动开发与调试体系构建——1.驱动开发的环境准备

    目录/参考资料:https://www.cnblogs.com/railgunRG/p/14412321.html 系统基础环境 开发环境 win7下开发驱动需要安装vs,这里使用2017. 安装vs ...