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

简介: 使用 cProfile 和火焰图调优 Python 程序性能

Python 中有一个第三方包(见参考文献)可以直接从 cProfile 的结果生成火焰图:

  1. 在原有的代码中加上一句: pr.dump_stats("pipeline.prof")
  2. 调用该工具: flameprof pipeline.prof>pipeline.svg

然后打开 SVG 文件就可以了:640 (6).jpg其中火焰的宽度代表了运行的时长,我们现在的优化目标就是这些耗时比较长的步骤。

可以看大其中 mysql 的访问占了绝对的大头,按理说跑100次的话,不应该每次都花费时间在建立连接上啊,这里一定有问题。经过排查发现在某处链接是使用了 close_old_connections 来保证不会抛出数据库断开的异常,这还是在头条带来的习惯。。closeoldconnections 的功能是关闭已经失效的链接,看来我的理解还是有误的。先把这块删掉,最终解决应该是这块放到一个队列里,统一存入数据库。

去掉之后:640 (7).jpg现在的大头又变成 lxml 的,又动了优化它的心思,lxml 是 libxml2 的一个 Python binding,查了下应该是最快的 html parser 了,这块真的没有什么优化空间。盯了一会儿,眼睛最终看到了一个小角落:  640 (8).jpg

一个正则匹配居然占用了 8% 的运行时间,太不像话了。老早之前就听说 Python 的标准库正则性能不行,现在才发现原来是真的挺差劲的。Python 标准库的 re 模块采用的是 PCRE 的处理方式,而采用 NFA 的处理方式的正则要快很多,这块还需要再看一下。不过眼下倒是可以直接换一个库来解决。regex 模块是 re 模块的一个 drop-in replacement.

pip install regex and importregexasre,就搞定了640 (9).jpg可以看到正则那块直接消失了。提升还是很大的。时间不早了,当天的优化就到此结束了。上线之后,积压一下子就下去了:640 (10).jpg

后记

要想调试的时候方便,在写代码的时候就要注意,尽量使自己的代码 mock-friendly 一点。如果需要引入外部的数据库、服务、API等等各种资源,最好有一个开关或者选项能够不加载外部资源,或者至少能够很方便地 mock 这些外部服务,这样方便对每一个小单元进行 profile。

总有人吐槽 Python 的性能低下,但是 Python 本来就不是做计算任务的呀,Python 是一门胶水语言,是用来写业务逻辑的,而不是用来写CPU密集的算法的。事实上复杂的解析一般都会用 C++ 这种硬核语言来写了,比如 numpy TensorFlow lxml。大多数程序员一天 90% 的工作除了和产品经理撕逼以外,也就是在写 CRUD,也就是调用这些包。所以瓶颈一般在 IO 上而不在 CPU 上,而解决 IO 的瓶颈手段就多了,Python 中至少有 多进程、多线程、AsyncIO、Gevent 等多种方法。不过方法多其实也是一个弊端,这几种方法可以说是基本互不兼容,对各种第三方库的支持也参差不齐。而 Go 在这方面就做地很好了,语言直接内置了 go 关键字,甚至都不支持多线程。所有的库都是支持一个统一的并发模型,对于使用者来说更简单。

Zen of Python 中有一句:There should be one way -- preferably only one way -- to do a thing. 这点上 Python 本身没有做到,反倒是 Go 实践地非常好。

扯远了,程序的瓶颈其实不外乎CPU、内存和 IO 三个方面,而 cProfile 和火焰图是判断 CPU 瓶颈的一把利器。

后面还发现了一些性能瓶颈,也列在这里:

  1. yaml 的反序列化时间过长。解决方法是添加了一个 Expiring LRU Cache,不要每次都去加载,当然牺牲的是一点点内存,以及当规则变更时会有一些延迟,不过都是可以接受的。之前早就听人说 Thrift 的序列化性能相比 Protobuf 太低,现在想想序列化和反序列化还真是一个很常见的性能瓶颈啊。
  2. 存储使用了 360 的 pika,pika 可以理解为一个基于 rocksdb 的硬盘版 redis。最开始的时候没多想,随便找了台机器搭了起开,把上面的问题解决之后,pika 的延迟很快大了起来,机器的监控也显示 IO 基本被打满了。这时候才发现原来这台机器没有用 SSD,果断换了 SSD 问题基本解决了。如果再有问题可能就需要集群了。

性能这个问题其实是典型的木桶理论的场景,系统的整体性能是由最差的一块决定的。所以也是一个不断迭代的过程。

目录
相关文章
|
3天前
|
搜索推荐 Python
快速排序:Python 中的速度之王,揭秘它的递归魔法与性能极限!
【7月更文挑战第12天】快速排序**是高效排序算法,基于分治策略。它选择基准值,将数组分成小于和大于基准的两部分,递归地对两部分排序。
|
1天前
|
存储 大数据 数据处理
优化Python中的数据处理效率:使用生成器提升性能
在Python编程中,有效的数据处理是提升性能和效率的关键。本文将探讨如何利用生成器(generator)优化数据处理过程,通过实例展示生成器如何在内存效率和执行速度上带来显著提升。
|
3天前
|
算法 Python
Python 大神修炼手册:图的深度优先&广度优先遍历,深入骨髓的解析
【7月更文挑战第12天】Python进阶必学:DFS和BFS图遍历算法。理解图概念,用邻接表建无向图,实现DFS和BFS。DFS适用于查找路径,BFS解决最短路径。通过实例代码加深理解,提升编程技能。
15 4
|
3天前
|
网络协议 Python
Scapy一个强大的 Python 程序(一)
Scapy是Python的网络数据包操作工具,用于创建、分析和发送网络包。启动Scapy需以管理员权限运行`sudo scapy`。在交互式环境中,可构建自定义数据包,如设置IP包的`ttl`、`src`和`dst`。通过`/`叠加协议层,如IP和TCP。发送数据包示例:构造向`www.slashdot.org`的HTTP GET请求。Scapy还能用于嗅探、过滤和修改数据包,功能强大。
|
3天前
|
算法 Python
逆袭之路!用 Python 玩转图的 DFS 与 BFS,让数据结构难题无处遁形
【7月更文挑战第12天】图的遍历利器:DFS 和 BFS。Python 中,图可表示为邻接表或矩阵。DFS 沿路径深入,回溯时遍历所有可达顶点,适合找路径和环。BFS 层次遍历,先近后远,解决最短路径问题。两者在迷宫、网络路由等场景各显神通。通过练习,掌握这些算法,图处理将游刃有余。
10 3
|
2天前
|
网络协议 安全 Python
Scapy一个强大的 Python 程序(二)
Scapy是Python的网络安全工具,可用于创建和修改网络包
|
2天前
|
存储 算法 Python
“解锁Python高级数据结构新姿势:图的表示与遍历,让你的算法思维跃升新高度
【7月更文挑战第13天】Python中的图数据结构用于表示复杂关系,通过节点和边连接。常见的表示方法是邻接矩阵(适合稠密图)和邻接表(适合稀疏图)。图遍历包括DFS(深度优先搜索)和BFS(广度优先搜索):DFS深入探索分支,BFS逐层访问邻居。掌握这些技巧对优化算法和解决实际问题至关重要。**
9 1
|
5天前
|
存储 算法 调度
惊呆了!Python高级数据结构堆与优先队列,竟然能这样优化你的程序性能!
【7月更文挑战第10天】Python的heapq模块实现了堆和优先队列,提供heappush和heappop等函数,支持O(log n)时间复杂度的操作。优先队列常用于任务调度和图算法,优化性能。例如,Dijkstra算法利用最小堆加速路径查找。堆通过列表存储,内存效率高。示例展示了添加、弹出和自定义优先级元素。使用堆优化程序,提升效率。
15 2
|
5天前
|
存储 算法 搜索推荐
Python高手必备!揭秘图(Graph)的N种风骚表示法,让你的代码瞬间高大上
【7月更文挑战第10天】在Python中,图数据结构通过邻接矩阵、邻接表、边列表和邻接集来表示,用于社交网络分析和路径查找等。邻接矩阵用二维数组存储连接,邻接表仅存储每个节点的邻居,节省空间。边列表列出所有边,而邻接集用集合确保邻居唯一性。选择合适表示法能提升代码效率和可读性,展现编程技巧。
12 1
|
6天前
|
Python
Python模拟火焰文字效果:炫酷的火焰字动效
Python模拟火焰文字效果:炫酷的火焰字动效
13 1