python程序优化笔记
最近要优化一个python程序,提高程序的执行效率,降低程序的资源占用。在优化过程中用到了一些优化的工具,并且使用了一些优化的方法,在这里做一个记录。
性能测试工具
在进行程序优化时,需要检测两个方面的内容,一个是程序的内存使用,另一个是程序的执行时间。下面介绍两个用于进行Python性能检测的工具,memory_profiler,用于检测程序的内存使用情况;cProfile,用于检测代码执行次数以及执行的时间。
memory_profiler
这个包不是python的标准库中的内容,因此在使用前需要下载,使用pip工具进行下载
$ pip install memory_profiler
下载完成后,按照下述方法使用
from memory_profiler import profile
import time
@profile #为需要进行内存分析的函数加上装饰器
def my_func():
a = [1] * (10 ** 6)
b = [2] * (2 * 10 ** 7)
time.sleep(10)
del b
del a
print "+++++++++"
if __name__ == '__main__':
my_func()
然后在执行程序时,加上下面的参数,即可获得程序执行时的内存消耗信息。
$ python -m memory_profiler del3.py
+++++++++
Filename: del3.py
Line # Mem usage Increment Line Contents
================================================
2 10.293 MiB 0.000 MiB @profile
3 def my_func():
4 17.934 MiB 7.641 MiB a = [1] * (10 ** 6)
5 170.523 MiB 152.590 MiB b = [2] * (2 * 10 ** 7)
6 170.527 MiB 0.004 MiB time.sleep(10)
7 17.938 MiB -152.590 MiB del b
8 10.305 MiB -7.633 MiB del a
9 10.309 MiB 0.004 MiB print "+++++++++"
cProfile
这个包是python标准库所包含的一个包,使用时不要下载额外的组件。这个包的使用方法很简单,直接在Python后面加上相应的参数即可。
$ python -m cProfile -o examine_cost.txt your_code.py
其中-o后面加的是时间分析的结果文件。这个结果文件是一个二进制文件,需要使用python内部的方法来读取。
>>> import pstats
>>> p = pstats.Stats("examine_cost.txt")
>>> p.strip_dirs().sort_stats("cumulative").print_stats()
便可以到所有代码运行时间、次数等信息了。
优化思路
整个优化过程走了不少的弯路,但是也学习到了不少的相关知识,现在记录如下。
多进程优化
在程序执行速度很慢的情况下,一个最简单的优化思路便是多开几个线程,利用服务器的多个核心,并行执行计算。但是在python中,由于CTL的存在,多线程程序并不能充分利用服务器的多个核心。因此,我们往往通过使用python开启多个进程的方式来利用服务器的核心。python开启多进程的方法在python多进程有十分详细的介绍。
python多进程最大的缺点便是程序在主进程与子进程之间不能传递除基本类型外的其他类型参数,如果一定需要传递这样的参数必须使用管道或者进程队列,而这两个的传输速度比较慢,可能会降低优化的效果。因此常见的使用方式是将所需要用到的数据以文件或者数据库参数配置的方式传递到子进程中,由子进程来实现数据读取并进行处理。
在进行了这方面的优化后,重新运行程序发现,由于程序需要在一个非常大的道路网中进行全局查找,因此每个子进程都要加载这个道路网数据,占用的内存量非常大,服务器有可能无法支撑如此大的内存开销。
内存优化
使用内存分析工具发现,内存占用过大的原因主要是读取的道路网数据未能及时释放。在我的程序中,是使用Python的ogr库读取mifg格式的路网数据。其基本的代码如下:
import ogr
def read_mifg(file_path):
#以下为示意代码,所有的意外情况判断全部省去了
ds_file = ogr.Open(file_path)
lyr_file = ds_file.GetLayer(0)
for feature in lyr_file:
#对feature进行处理
feature.Destroy() #释放feature占用的内存
#下述方式可以获取layer中第i个feature数据,但是执行速度非常慢(相当于O(n)),一般情况不推荐使用
#lyr_file.GetFeature(i)
在之前的代码中,缺少Destroy这个步骤,因此造成程序一致占有这部分内存,所以内存使用量过大。
python的一个坑
在经过上述优化后,程序的运行时间和资源占用量已经降低了不少。但是我感觉自己的程序不应该使用这么长的时间进行计算。因此使用了运行时间检测工具对程序运行时间进行了检测,发现有一行代码的执行时间非常长。
if sw_id in sw_id_trfcsign_dic.keys():
在凝视了这个代码几分钟后,突然意识到,dict的keys()方法返回的是列表而不是一个set,因此这一行代码的效率从O(logn)硬生生降低为了O(n)运行速度差了2000倍!把上述代码更改后:
if sw_id_trfcsign_dic.has_key(sw_id):
发现前面的优化完全可以不做……
另外,因为我的代码是使用python2写的,因此只能用has_key这样不太Python的写法,对于Python3,可以写的更加优雅:
if sw_id in sw_id_trfcsign_dic