背景
前面我们说到 Python 之父结束了自己的退休生活,出山着手解决 Python 解释器的性能问题。并于 2022-10-24 发布了 Python-3.11 版本,综合性能提升了 22%。
作为一个性能工程师 + Python 深度使用者,一发布我就去看了 3.11 版本的优化列表,有失望,有惊喜,有疑惑。
失望是因为像 GIL锁,JIT 这些都不是新版本的优化项;惊喜是因为前面几个重点问题都没有解决的情况上性能就提升了 22%,那解决了还了得;疑惑是不知道官方是怎定位性能问题的,毕竟他列出的那些优化项都不容易发现。
疑惑
为了说清楚 Python 性能优化上,怎么找热点的疑惑,我们可以看一下 Java 这门语言。Java 的性能分析工具链非常的健全,比如通过 async-profiler 这个工具我可以清楚的知道某个特定函数占用的时间,占程序总时间的百分比是多少。直观上来看就是火焰图中,哪个函数,躺的越平,躺的越宽,它的问题就越是大。

上面是一个通过 async-profiler 打出的火焰图,通过这个开发者可以清楚的看到哪个函数占用的时间多,进而有针对性地优化。
还是说回 Python,完善的性能分析工具链,有助于工程师发现问题。然而 Python 在这个方面表现要差不少,比如说火焰图中看不到 Python 代码,只能看到解释器的 C 代码;也就是没有办法直接观测到应用层的热点代码。
成熟的人要学会自己与自己和解
以 Java 做参考,确实能发现 Python 的不足。 记得有一天在公司食堂吃饭,排队时我突然就悟了;之前做 MySQL DBA 的经历告诉我,虽然我打 MySQL 的火焰图也看不到 MySQL 执行的 SQL 语句;也是只能看到 C/C++ 的函数堆栈,但这并不影响我解决问题。
是不是说只要工夫深,光是看到 C/C++ 的函数堆栈也能向上推算 SQL 语句或 Python 代码呢?看来还是人外有人啊。
Python-3.12 开始补齐短板
好不容易,11 月自己和自己和解了,官方 12 月就出了一个新功能,现在我们能在火焰图中看到 Python 代码的函数堆栈了。
具体来说就是 Python-3.12 加上了 《Python support for the Linux perf profiler》这个新特性。所有特性都在其出生时就标好了价格,这个特性的价格是,我们要在编译安装解释器的时候显示的指定开启这个功能。编译安装时的语句如下
./configure --prefix=/usr/local/python-3.12 CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
&& make -j 2
&& make instal其中 CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" 参数就是用来开启这个功能的。
体验新特性
第一步就是构造出一个用肉眼就能看出热点的代码。
#!/usr/bin/env python3
# -*- coding: utf8 -*-
def foo():
i = 0
for j in range(100):
i = j + 100
def bar():
i = 0
for j in range(200):
i = j + 100
def main():
for i in range(100000):
foo()
bar()
if __name__ == "__main__":
main()在这个例子中 foo 函数和 bar 函数是同构的,唯一不同的是 bar 的运行量是 foo 的一 200% ;main 函数又分别调用了他们 10w 次。
就上面的代码而言不用什么性能分析工具,直接上肉眼就能发现问题;但是线上一些机器学习的程序动不动就是 100w 行代码起步,用肉眼是看不过来的。
虽说这次的代码量只能说是一只麻雀,但不妨碍我们以鲲鹏视之,下面简单的走一下性能分析的流程。
# 执行程序并采样
perf record -F 9999 -g -o perf.data /usr/local/python-3.12/bin/python3 main.py
# 用采集到的数据画图
perf script | /usr/local/FlameGraph/stackcollapse-perf.pl > out.perf-folded
/usr/local/FlameGraph/flamegraph.pl out.perf-folded > perf.svg最终的输出是一个 svg 图片,在浏览器里面可以直接打开,整体上看如下。

1、main 函数的耗时占总时间的 98.54%

2、foo 函数耗时占总时间的 31.02%

3、bar 函数耗时占总时间的 65.55%

现在我们能在火焰图上直观地看到,哪个函数耗时高。想像一下反正是看图,就算是面对大项目,代码根本看不过来,发现热点也应该不是什么大问题。
最后
可能是因为还是测试版本吧,现在看到的火焰图还不是特别干净,希望后面的版本能去掉其它无关信息。