- 1 建议 79:了解代码优化的基本原则
- 2 建议 80:借助性能优化工具
- 3 建议 81:利用 cProfile 定位性能瓶颈
- 4 建议 82:使用 memory_profiler 和 objgraph 剖析内存使用
- 5 建议 83:努力降低算法复杂度
- 6 建议 84:掌握循环优化的基本技巧
- 7 建议 85:使用生成器提高效率
- 8 建议 86:使用不同的数据结构优化性能
- 9 建议 87:充分利用 set 的优势
- 10 建议 88:使用 multiprocess 克服 GIL 的缺陷
- 11 建议 89:使用线程池提高效率
- 12 建议 90:使用 C/C++ 模块扩展高性能
- 13 建议 91:使用 Cython 编写扩展模块
1 建议 79:了解代码优化的基本原则
- 优先保证代码是可工作的
- 权衡优化的代价
- 定义性能指标,集中力量解决首要问题
- 不要忽略可读性
2 建议 80:借助性能优化工具
常见的性能优化工具有 Psyco
、Pypy
和 cPython
等
Psyco
是一个Python扩展模块,可以加快任何Python代码的执行速度,目前已停止维护~
Pypy
作为CPython的替代(平均效率是CPython的4.2倍),支持大多数常用的Python标准库
3 建议 81:利用 cProfile 定位性能瓶颈
profile
是 Python 的标准库,可以统计程序里每一个函数的运行时间,并且提供了多样化的报表,而 cProfile
则是它的 C 实现版本,剖析过程本身需要消耗的资源更少。所以在 Python3 中,cProfile
成为默认的性能剖析模块。这些统计数据可通过 pstats
模块格式化为报告。
示例:
import cProfile
import re
cProfile.run('re.compile("foo|bar")')
结果如下:
197 function calls (192 primitive calls) in 0.002 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.001 0.001 <string>:1(<module>)
1 0.000 0.000 0.001 0.001 re.py:212(compile)
1 0.000 0.000 0.001 0.001 re.py:268(_compile)
1 0.000 0.000 0.000 0.000 sre_compile.py:172(_compile_charset)
1 0.000 0.000 0.000 0.000 sre_compile.py:201(_optimize_charset)
4 0.000 0.000 0.000 0.000 sre_compile.py:25(_identityfunction)
3/1 0.000 0.000 0.000 0.000 sre_compile.py:33(_compile)
具体可参考Python 分析器
4 建议 82:使用 memory_profiler
和 objgraph
剖析内存使用
Python 还提供了一些工具可以用来查看内存的使用情况以及追踪内存泄漏(如 memory_profiler
、objgraph
、cProfile
、PySizer
及 Heapy
等),或者可视化地显示对象之间的引用(如 objgraph
),从而为发现内存问题提供更直接的证据。
memory_profiler
是一个用于监控进程的内存消耗的python模块,能够逐行分析python程序的内存消耗。它是一个纯python模块,依赖于psutil模块。
memory_profiler
使用示例:
# example.py
@profile
def my_func():
a = [1] * (10 ** 6)
b = [2] * (2 * 10 ** 7)
del b
return a
if __name__ == '__main__':
my_func()
python -m memory_profiler example.py
# output
Line # Mem usage Increment Occurences Line Contents
============================================================
3 38.816 MiB 38.816 MiB 1 @profile
4 def my_func():
5 46.492 MiB 7.676 MiB 1 a = [1] * (10 ** 6)
6 199.117 MiB 152.625 MiB 1 b = [2] * (2 * 10 ** 7)
7 46.629 MiB -152.488 MiB 1 del b
8 46.629 MiB 0.000 MiB 1 return a
objgraph
是一个用于可视化地探索Python对象的模块。
objgraph
使用示例1(生成对象 x 的引用关系图):
import objgraph
x = ['a', '1', [2, 3]]
objgraph.show_refs([x])
objgraph
使用示例1(显示对象数最多的三个类型):
objgraph.show_most_common_types(limit=3)
# output
function 28053
dict 16973
tuple 12292
memory_profiler项目地址 objgraph项目地址 PySizer项目地址(已停止维护) Heapy项目地址(已停止维护)
5 建议 83:努力降低算法复杂度
随着计算硬件资源发展,降低算法的时间复杂度更被重视
计算算法的时间复杂度时,既要注意平均时间复杂度,也要注意最差时间复杂度
O(1) < O(log * n) < O(n) < O(n log n) < O(n^2) < O(c^n) < O(n!) < O(n^n)
6 建议 84:掌握循环优化的基本技巧
- 减少循环内部的计算
- 显式循环改为隐式循环(比如用公式求解代替循环求解)
- 在循环中尽量引用局部变量(搜索查询更快)
- 嵌套循环时,尽量将内层循环的计算往上层移
7 建议 85:使用生成器提高效率
如果一个函数体中包含有 yield 语句,则称为生成器(generator)。 生成器是一种特殊的迭代器(iterator),也可以称为可迭代对象(iterable)。
生成器的优点:
- 代码简洁优雅,生成迭代器的方式方便
- 利用了惰性计算(Lazy evaluation)的特性,节省内存空间,提高效率
- 使得协同程序(协程)更容易实现
8 建议 86:使用不同的数据结构优化性能
插入、删除频繁的场景,可以考虑双端队列
deque
,它同时具备栈和队列的特性,在两端插入和删除时操作的复杂度为 O(1) ,目前deque
对象被封装到了collections
模块中,具体可参考deque 对象文档说明排序需求多的场景,可以考虑借助
bisect
模块维持常见数据结构的有序性,保持列表有序需要付出额外的成本,需要根据具体情况进行取舍,模块的进一步说明请参阅bisect文档--数组二分查找算法寻找最值的场景,可以考虑借助
heapq
模块,它借助二叉树结构实现了优先队列算法,并确保最小值永远在根节点,也就是列表的第一个元素,模块的进一步说明请参阅heapq文档--堆队列算法当容器存储的元素是同一类型时,可以考虑用
array
数据结构优化程序性能
9 建议 87:充分利用 set 的优势
Python 中集合是通过 Hash 算法实现的无序不重复的元素集。
计算交集、并集、补集的平均复杂度为 O(n)
,最差时间复杂度为 O(n^2)
,远好于list迭代计算
10 建议 88:使用 multiprocess
克服 GIL 的缺陷
1_study/ComputerScience/programming/进程、线程与协程#进程
11 建议 89:使用线程池提高效率
线程的生命周期分为 5 个状态:创建、就绪、运行、阻塞和终止。
线程的生命周期中真正占有 CPU 的只有运行、创建和销毁这 3 个状态。
线程池能避免线程的反复创建,从而节省线程创建和销毁的开销,带来更好的性能和系统稳定性
线程池技术适合处理的应用场景:
- 突发性大量请求
- 需要大量线程来完成任务、但任务实际处理时间较短
本小节提及的threadpool
模块比较老了,目前仅推荐一些旧项目继续使用
现在更常用的是concurrent.futures
模块中的ThreadPoolExecutor
类,如需进一步了解可参阅ThreadPoolExecutor文档说明
12 建议 90:使用 C/C++ 模块扩展高性能
Python 具有良好的可扩展性,利用 Python 提供的 API,如宏、类型、函数等,可以让 Python 方便地进行 C/C++ 扩展,从而获得较优的执行性能。所有这些 API 却包含在 Python.h 的头文件中,在编写 C 代码的时候引入该头文件即可。
如需进一步了解可参阅Python/C API 参考说明
13 建议 91:使用 Cython 编写扩展模块
Python-API
让大家可以方便地使用 C/C++
编写扩展模块,从而通过重写应用中的瓶颈代码获得性能提升。但是,这种方式仍然有几个问题:
掌握 C/C++ 编程语言、工具链有巨大的学习成本
即便是 C/C++ 熟手,重写代码也有非常多的工作,比如编写特定数据结构、算法的 C/C++ 版本,费时费力还容易出错
所以整个 Python 社区都在努力实现一个 ”编译器“,它可以把 Python 代码直接编译成等价的 C/C++ 代码,从而获得性能提升。这类工具有 Pyrex、Py2C 和 Cython
等。而从 Pyrex 发展而来的 Cython 是其中的集大成者。
Pyrex项目地址(已停止维护) Py2C项目地址 Cython项目地址