zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Python-3.12 性能分析特性

Python性能 分析 特性 3.12
2023-06-13 09:16:00 时间

背景

前面我们说到 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%

现在我们能在火焰图上直观地看到,哪个函数耗时高。想像一下反正是看图,就算是面对大项目,代码根本看不过来,发现热点也应该不是什么大问题。


最后

可能是因为还是测试版本吧,现在看到的火焰图还不是特别干净,希望后面的版本能去掉其它无关信息。