使用 cProfile 和火焰图调优 Python 程序性能(上)

本文涉及的产品
云解析DNS-重点域名监控,免费拨测 20万次(价值200元)
简介: 使用 cProfile 和火焰图调优 Python 程序性能

前几天调试程序,发现 QPS 总是卡在 20 左右上不去。开始以为是 IO 问题,就多开了些并发,然并卵,这才想到可能是 CPU 的问题。看了看监控,发现程序某一步的延迟在 400ms 左右,而且这一步是 CPU 密集的。当时开了 4 台双核的机器:(1s / 400ms) * 2 * 4 = 20 啊。看来需要优化下这一步的代码了,那么第一步就是找到可以优化的地方640.jpg测量程序的性能之前并没有实际做过,Google 了一番,感觉标准库的 cProfile 似乎值得一试。

要测量的代码逻辑也很简单,使用 lxml 解析 HTML,然后提取一些字段出来,这些基本都是调用的 C 库了,解析的算法也不在 Python 中。看起来似乎没有什么能改进的地方,不管怎样,还是先跑一下吧。

cProfile 有多种调用方法,可以直接从命令行调用:

python -m cProfile -s tottime your_program.py

其中的 -s 的意思是 sort。常用的 sort 类型有两个:

  1. tottime,指的是函数本身的运行时间,扣除了子函数的运行时间
  2. cumtime,指的是函数的累计运行时间,包含了子函数的运行时间

要获得对程序性能的全面理解,经常需要两个指标都看一下。

不过在这里,我们并不能直接使用命令行方式调用,因为我的代码中还需要一些比较繁重的配置加载,如果把这部分时间算进去了,多少有些干扰,那么我们也可以直接在代码中调用 cProfile。

使用 cProfile 的代码如下:

import cProfile, pstats, io
pr = cProfile.Profile()
pr.enable()
extractor.extract(crawl_doc=doc, composition=PageComposition.row, rule=rule)
pr.disable()
s = io.StringIO()sortby = "cumtime" # 仅适用于 3.6, 3.7 把这里改成常量了
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print(s.getvalue())

把需要 profile 的代码放到 pr.enable 和 pr.disable 中间就好了。注意这里我们使用了 cumtime 排序,也就是累计运行时间。

结果如下:640 (1).jpg我们可以看到总的运行时间是 200ms,而其中红框内的部分就占了 100ms! 如果能够优化调的话,性能一下子就能提高一倍。红框内的代码是做什么的呢?我们知道解析一个 html 文档,第一步是建立 DOM 树,通常情况下,我们可能会从其中抽取一些链接。在网页中,链接不一定是绝对路径,也可能是 /images/2018-12-31-xxx.jpg 这样的相对路径。lxml 库帮我们做了一个贴心的默认值,那就是在构造 DOM 树的时候,根据传入的 url 来吧页面中的所有 url 都重写成绝对路径。看起来这是个很贴心的功能,但是在这里却成了性能瓶颈。为什么很耗时呢?大概是因为需要遍历整个 DOM 树,重写所有的链接吧。这显然不是我们需要的,我们只需要把抽取之后的链接还原成绝对路径就好了,而不是事先把所有链接都重写一遍。所以在这里我们直接去掉这个功能就好了。

修改代码之后,再次运行 profile 脚本,时长变成了 100ms:640 (2).jpg这时候我们接着看,程序中下一个比较大头的时间占用:jsonfinder 和 json decode/encode。

jsonfinder 是一个有意思的库,它自动从 HTML 中查找 json 字符串并尝试解析,但是有时候也不太准。经常为了找到特定的值,还是需要使用正则。那么对于这个可有可无的功能,性能有这么差,还是删掉好了。

通过删代码,现在性能已经是原来的四倍了。

这时候发现代码里面有正则还挺花费时间的,不过还好,暂时先不管了。640 (3).jpg刚刚都是只运行了一遍,测量结果难免有随机性,必定有失偏颇,实际上应该使用多个测试用例,成千上万次的跑,才能得到一个比较准确地结果。

上面这个小步骤基本没有什么可以优化的了,下面我们把优化目标扩大一点,并把次数先定为100.

下面这种图是按照 tottime 来排序的:640 (4).jpg注意其中最耗时的步骤是 parseUnicodeDoc,也就是建树了,这是符合预期的。然而旁边的 ncalls 一栏却不太对劲了。我们明明只运行了 100 次,为什么这个函数调用了 300 次呢?显然代码中有重复建树的地方,也就是有隐藏的 bug。这种地方不经过 profile 很难浮现出来,因为程序本身的逻辑是对的,只是比较耗时而已。640 (5).jpg优化之后,终于变成了 100. 从 cProfile 的表格现在已经看不出什么结果来了,下一步我们开始使用火焰图,可视化往往能让我们更容易注视到性能瓶颈。(为什么不一开始就用火焰图呢?因为我以为很麻烦。。实际很简单)

目录
相关文章
|
4月前
|
人工智能 Linux 开发工具
Python从零到一:手把手带你写出第一个实用程序
Python语法简洁易懂,适合编程新手入门。它广泛应用于人工智能、自动化办公、Web开发等领域。学习Python可快速搭建项目,拥有丰富库支持和强大社区资源。通过本教程,你将掌握基础语法、环境搭建、程序逻辑控制及实战项目开发,开启编程之旅。
436 0
|
6月前
|
机器学习/深度学习 数据采集 算法
Python AutoML框架选型攻略:7个工具性能对比与应用指南
本文系统介绍了主流Python AutoML库的技术特点与适用场景,涵盖AutoGluon、PyCaret、TPOT、Auto-sklearn、H2O AutoML及AutoKeras等工具,帮助开发者根据项目需求高效选择自动化机器学习方案。
643 1
|
8月前
|
数据采集 测试技术 C++
无headers爬虫 vs 带headers爬虫:Python性能对比
无headers爬虫 vs 带headers爬虫:Python性能对比
|
3月前
|
设计模式 决策智能 Python
Python条件控制:让程序学会"思考"的魔法
本文深入浅出地讲解Python条件控制,从基础if语句到多分支、嵌套结构,再到简洁的三元表达式与Python 3.10新增的match-case模式匹配,结合电商折扣、会员等级、ATM系统等实战案例,全面掌握程序“智能决策”的核心逻辑。
409 0
|
4月前
|
数据采集 存储 Web App开发
Python爬虫库性能与选型实战指南:从需求到落地的全链路解析
本文深入解析Python爬虫库的性能与选型策略,涵盖需求分析、技术评估与实战案例,助你构建高效稳定的数据采集系统。
393 0
|
7月前
|
人工智能 并行计算 开发者
CUDA重大更新:原生Python可直接编写高性能GPU程序
NVIDIA在2025年GTC大会上宣布CUDA并行计算平台正式支持原生Python编程,消除了Python开发者进入GPU加速领域的技术壁垒。这一突破通过重新设计CUDA开发模型,引入CUDA Core、cuPyNumeric、NVMath Python等核心组件,实现了Python与GPU加速的深度集成。开发者可直接用Python语法进行高性能并行计算,显著降低门槛,扩展CUDA生态,推动人工智能、科学计算等领域创新。此更新标志着CUDA向更包容的语言生态系统转型,未来还将支持Rust、Julia等语言。
525 3
CUDA重大更新:原生Python可直接编写高性能GPU程序
|
6月前
|
PyTorch 算法框架/工具 C++
人工智能算法python程序运行环境安装步骤整理
本教程详细介绍Python与AI开发环境的配置步骤,涵盖软件下载、VS2017安装、Anaconda配置、PyCharm设置及组件安装等内容,适用于Windows系统,助你快速搭建开发环境。
|
7月前
|
网络协议 API 开发者
分析http.client与requests在Python中的性能差异并优化。
合理地选择 `http.client`和 `requests`库以及在此基础上优化代码,可以帮助你的Python网络编程更加顺利,无论是在性能还是在易用性上。我们通常推荐使用 `requests`库,因为它的易用性。对于需要大量详细控制的任务,或者对性能有严格要求的情况,可以考虑使用 `http.client`库。同时,不断优化并管理员连接、设定合理超时和重试都是提高网络访问效率和稳定性的好方式。
176 19
|
6月前
|
网络协议 API Python
解析http.client与requests在Python中的性能比较和改进策略。
最后,需要明确的是,这两种库各有其优点和适用场景。`http.client` 更适合于基础且并行的请求,`requests` 则因其易用且强大的功能,更适用于复杂的 HTTP 场景。对于哪种更适合你的应用,可能需要你自己进行实际的测试来确定。
175 10

推荐镜像

更多