定位程序性能瓶颈
分析工具:profile,cProfile,PyCharm工具(简单,方便),还有line_profiler,pprofile
图形化工具:gprof2dot
对保存结果分析:pstats
-
profile的使用
profile 的使用非常简单,只需要在使用之前进行 import 即可。具体实例如下:
import profile def profileTest(): num = 1 for i in range(100): num += 1 return num if __name__ == '__main__': profile.run("profileTest()")
得到的结果如下:
其中输出每列的具体解释如下:
- ncalls:表示函数调用的次数;
- tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
- percall:(第一个 percall)等于 tottime/ncalls;
- cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时
- percall:(第二个 percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
- filename:lineno(function):每个函数调用的具体信息;
如果需要将输出以日志的形式保存,只需要在调用的时候加入另外一个参数。如 profile.run(“profileTest()”,”testprof”)
。
-
cProfile的使用
相比于其它内置的分析器(profile 或 hotshot),cProfile 对系统的开销更少。
使用方法非常简单,一条命令即可:
python -m cProfile test.py
如果要将分析结果保存到文件,则:
python -m cProfile -o result test.py
输出结果与profile的输出结果一样,参数解释也一样。
-
PyCharm的工具
也可以使用PyCharm的工具进行性能分析,而且还可以用图形化的方式查看(专业版PyCharm)。
如果使用的不是专业版,可以下载专业版进行安装,可自行上网查找免费使用教程
菜单栏Run -> Profile '[文件名]'即可运行,或者右键 Profile ‘[文件名]’,可得到表格结果以及图形化结果,详细说明见利用PyCharm的Profile工具进行Python性能分析
-
工具pstats分析
对于 profile 和 cProfile 的剖析数据,如果以二进制文件的时候保存结果的时候,可以通过 pstats 模块进行文本报表分析,它支持多种形式的报表输出,是文本界面下一个较为实用的工具。使用非常简单:
import pstats p = pstats.Stats('testprof') p.sort_stats("name").print_stats()
其中 sort_stats() 方法能够对剖分数据进行排序, 可以接受多个排序字段,如 sort_stats(‘name’, ‘file’) 将首先按照函数名称进行排序,然后再按照文件名进行排序。常见的排序字段有 calls( 被调用的次数 ),time(函数内部运行时间),cumulative(运行的总时间)等。此外 pstats 也提供了命令行交互工具,执行 python – m pstats 后可以通过 help 了解更多使用方式。
-
line_profiler(未尝试)
这个分析器可以提供逐行水平的负载信息。这是通过 C 语言用 Cython 实现的,要使用这个工具,你首先需要通过 pip 添加:
pip install pip install Cython ipython==5.4.1 line_profiler(CPython2)
。你需要在你想分析的函数上加上一个装饰@profile
kernprof -l test.py python -m line_profiler test.lprof
-
pprofile(未尝试)
据作者介绍,pprofile 是一个「行粒度的、可感知线程的确定性和统计性纯 Python 分析器。
它的灵感来源于 line_profiler,修复了大量缺陷,但因为其完全是用 Python 写的,所以也可以通过 PyPy 使用。和 cProfile 相比,使用 CPython 时分析的时间会多 28 倍,使用 PyPy 时的分析时间会长 10 倍,但具有粒度更大的细节水平。
对于line_profile和pprofile想有更多的了解的,可见代码优化指南:人生苦短,我用Python
查找可优化代码
- 对于代码中的重复逻辑进行合并
- 对代码中的冗余逻辑进行删除
- 调用次数最多的语句或者占用时间最长的语句可否使用高效率库进行替换
Python代码优化常见技巧
-
数据结构
列表和字典:Python 字典中使用了 hash table,因此查找操作的复杂度为 O(1),而 list 实际是个数组,在 list 中,查找需要遍历整个 list,其复杂度为 O(n),因此对成员的查找访问等操作字典要比 list 更快。
如果要查找一个元素在不在列表中,可将列表使用
dict.fromkeys()
转换为字典,再进行查找,会快很多。列表和集合:set 的 union, intersection,difference 操作要比 list 的迭代要快。因此如果涉及到求 list 交集,并集或者差的问题可以转换为 set 来操作。
-
对循环的优化
对循环的优化所遵循的原则是尽量减少循环过程中的计算量,有多重循环的尽量将内层的计算提到上一层。
-
充分利用 Lazy if-evaluation 的特性
python 中条件表达式是 lazy evaluation 的,也就是说如果存在条件表达式 if x and y,在 x 为 false 的情况下 y 表达式的值将不再计算。因此可以利用该特性在一定程度上提高程序效率。
-
字符串的优化
python 中的字符串对象是不可改变的,因此对任何字符串的操作如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串,因此这种持续的 copy 会在一定程度上影响 python 的性能。对字符串的优化也是改善性能的一个重要的方面,特别是在处理文本较多的情况下。
- 在字符串连接的使用尽量使用 join() 而不是 +
- 当对字符串可以使用正则表达式或者内置函数来处理的时候,选择内置函数。如 str.isalpha(),str.isdigit(),str.startswith((‘x’, ‘yz’)),str.endswith((‘x’, ‘yz’))
- 对字符进行格式化比直接串联读取要快,因此要使用占位符或者format(),不要使用 + 连接字符串
-
使用列表解析和生成器表达式
列表解析要比在循环中重新构建一个新的 list 更为高效,因此我们可以利用这一特性来提高运行的效率。
生成器表达式语法和列表解析类似,但是在大数据量处理时,生成器表达式的优势较为明显,它并不创建一个列表,只是返回一个生成器,因此效率较高。
以上介绍的只是对 Python代码本身做优化,当然也可以使用 PyPy
或者 Cython
将代码转换成其他语言提高效率,有兴趣者可以见参考文章链接。
参考文章
https://www.ibm.com/developerworks/cn/linux/l-cn-python-optim/
https://blog.csdn.net/xiemanR/article/details/69398057?utm_source=copy