关于提升python程序效率的一些思考

Posted 卖山楂啦prss

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于提升python程序效率的一些思考相关的知识,希望对你有一定的参考价值。

转载自:关于提升python程序效率的一些思考
作者:邱震宇


最近刚刚参加完达观数据的文本分类比赛,最后排17名,总的来说solo比赛还是挺累的。因此也没有时间更新专栏,本文就难得水一篇,讲一讲最近在工作中遇到的一些关于python编程的效率问题,希望对其他同学有一些启示,有些东西是网上其他博客就有的,感兴趣的同学可以自行搜索,深入学习。后续我会出一篇关于达观比赛的总结,至于代码开源,得看时间,毕竟上班族伤不起。。。orz

问题起因是这样的:当前有一个pandas的dataframe结构A,A存储了一个数据序列中的各个字段的值,A的每一行表示时刻t的序列数据的具体内容。当前,需要对A进行归一化整合,使之转化为一段hash后的字符串,中间当然省略了一些很复杂的处理逻辑(包括各种字符串处理,列表查询字典查询、hash转换等,注意加粗的两个操作,后面会提到)。一个dataframe里面的record数量从100+到5000+不等,DB中存有几十万的dataframe。

传统的单纯使用循环操作,逻辑代码如下:

for dataframe in dataframe_list:
    for index, row in dataframe .iterrows():
        do something complex........

可想而知,稍微懂开发的人肯定会说这个效率太慢了。事实上,确实很慢,万级别的数据我运行了整整24小时才跑完。这对于任何一个开发人员来说都是不能忍受的,所以要想办法提升效率。因此我做了以下一些尝试,将运行时间从24小时,逐步地缩减至了1小时。可能还有一些提升空间,如果有同学对pandas或者python高性能编程有一些好的见解,欢迎留言,一起探讨,这方面我还是一个菜鸟。

1、使用python的multiprocessing,主要是使用python的多进程并行,至于python的多线程,熟悉python的都知道,几乎没有传统多线程并行的优势,它的运行机制本质还是单线程的。主要是使用multiprocessing.pool的map方法,但是我的机器目前的cpu核数并不多,才四核,最后试了下,提升效果并不明显,而且最后数据整合花费的时间还是不少的,性价比比较低。

2、然后研究了pandas的map,apply,其中,map的对象是series,即我不能传整个dataframe进去。实际情况是我循环内的操作是要用dataframe里的每个字段的数据的,因此map不适用。apply的话,可以适用,同时由于新的pandas版本中支持了apply的function适用多个参数,因此用起来也挺方便,内层的循环可以去掉,换为:

dataframe.apply(function,args=(arg1,arg2,....),axis=1)

但是,实体测试之后,发现,这种方法几乎没有显著的提升,对于1000条数据的性能,只提升了几秒。后来,google查问题,发现pandas的apply方法对于rowwise的遍历性能是很差的,相反它对column_wise的列遍历倒是效果很好。因此和传统的for循环相比,apply on row几乎没有太大优势。

3、后来又想到了numpy的向量化操作,如果能将上述逻辑转化为向量单位进行操作,会不会有提升?因此就查到了numpy的vectorize方法:

Define a vectorized function which takes a nested sequence of objects or
numpy arrays as inputs and returns an single or tuple of numpy array as
output. The vectorized function evaluates `pyfunc` over successive tuples
of the input arrays like the python map function, except it uses the
broadcasting rules of numpy.

简单说就是能将一个function改造成numpy支持的向量化操作。当然,这个function的参数是有要求的,dataframe或者series肯定是不行的,因此我将本来传入的参数dataframe按列拆开来(幸好列数不多),将每列以ndarray的形式作为参数传入function,然后再用numpy的vectorize改造:

def do_function(array_1,array_2,.....):
    do something
    return something
vec_fun = np.vectorize(do_function)
result = vec_fun(array_1,array_2,.....)

经过实际测试,性能确实有提升,对于1000条数据的性能,提升了30-40秒左右。

4、上述提升仍然是不够的,注意到我外层还有一个循环,能够对这个循环进行优化呢?答案是可以的。python3中,对于列表递推式的实现有优化,内部使用了generator迭代器来实现,比普通的for循环的性能更好,因此外层的循环用列表递推式来优化

[vec_fun(dataframe) for dataframe in dataframe_list]

经过实地测试,性能确实也有提升,对于1000条数据的性能,提升了20秒左右。

5、到了这个时候,一开始我也不知道该怎么优化下去了。对于循环来说,我能做的已经做了,那么只能去循环内部查看哪些代码块可以进行性能优化。经过漫长的分析,终于发现有一个地方优化后,对于性能会有一个惊人的提升。之前一开始我提到了我的内部循环逻辑中,有列表查询和字典查询,这个就是制约我提升性能的最大敌人!由于功能需要,我需要维护很多字典和列表供字符串处理时进行一些查找操作。这些字典列表规模都很大,基本都有十万-百万级。如果用传统的if somethin in list来进行查找,它的时间效率应该是O(n),随着列表规模的增大而增大的。通过google搜索,发现Set数据结构能够优化查找的效率,它首先会对列表进行去重,然后使用类似于红黑树的数据结构,这样进行查找的时候,它的效率应该是O(logn)。因此将所有list用set重新构建后,发现性能有惊人的提升,对于1000条数据的性能,提升了200秒左右!

经过上述优化后,对于万级别的数据,本来需要24小时才能跑完,目前只需要1个小时的时间就能全部完成,优化效果显著。

当然,优化之路没有终点,我也只是对代码进行了初步的优化,可能还有一些奇技淫巧能够让python程序得到提升,希望对这方面有研究的同学来探讨。

以上是关于关于提升python程序效率的一些思考的主要内容,如果未能解决你的问题,请参考以下文章

关于自动化测试在测试效率提升过程中出现的一些问题的思考

关于python语言优化的一些思考

关于Python语言课程的思考

关于Python语言程序设计基础课程的思考

[ 测试思考 ] 效率提升测试工具开发的思考

关于GIL一点思考