当前位置: 首页 > 科技观察

Python性能优化的20条tips

时间:2023-03-12 14:15:25 科技观察

优化算法时间复杂度算法的时间复杂度对程序的执行效率有影响***,在Python中,可以通过选择合适的数据结构来优化时间复杂度,比如如list和set,查找一个元素的时间复杂度分别是O(n)和O(1)。不同的场景有不同的优化方式。一般来说,一般有分治法、分支定界法、贪心法、动态规划等思想。减少冗余数据,例如使用上三角或下三角来保存一个大的对称矩阵。稀疏矩阵表示用于以0元素为主的矩阵。合理使用copy和deepcopy对于dict、list等数据结构的对象,直接赋值使用引用的方式。在某些情况下,您需要复制整个对象。这种情况下可以使用copy包中的copy和deepcopy。这两个函数的区别在于后者是递归复制。效率也不同:(以下程序在ipython中运行)importcopya=range(100000)%timeit-n10copy.copy(a)#运行10次copy.copy(a)%timeit-n10copy.deepcopy(a)10loops,bestof3:1.55msperloop10loops,bestof3:151msperlooptimeit后面的-n表示运行次数,最后两行对应两次timeit的输出,下同。可以看出后者慢了一个数量级。使用dict或set查找元素。python的dict和set都是使用哈希表实现的(类似于c++11标准库中的unordered_map)。查找元素的时间复杂度为O(1)a=range(1000)s=set(a)d=dict((i,1)foriina)%timeit-n10000100ind%timeit-n10000100ins10000loops,bestof3:43.5nsperloop10000loops,bestof3:49.6nsperloopdict效率稍微高一点(并且占用更多空间)。generator(生成器)和yield的合理使用%timeit-n100a=(iforiinrange(100000))%timeit-n100b=[iforiinrange(100000)]100loops,bestof3:1.54msperloop100loops,bestof3:4.56msperloop使用()得到一个generator对象,所需的内存空间与列表的大小无关,所以效率会更高。在具体的应用中,比如set(iforiinrange(100000))会比set([iforiinrange(100000)])更快。但是对于需要循环遍历的情况:%timeit-n100a=(iforiinrange(100000))%timeit-n100b=[iforiinrange(100000)]100loops,bestof3:1.54msperloop100loops,bestof3:4.56msperloop后者效率更高,但是如果循环中断,使用生成器的好处是显而易见的。yield也用于创建生成器:defyield_func(ls):foriinls:yieldi+1defnot_yield_func(ls):return[i+1foriinls]ls=range(1000000)%timeit-n10foriiinyield_func(ls):pass%timeit-n10foriinnot_yield_func(ls):pass10loops,bestof3:63.8msperloop10loops,bestof3:62.9msperloop对于内存不是很大的list,可以直接返回一个list,但是可读性yield比较好(个人喜好)。python2.x内置的生成器函数包括xrange函数、itertools包等。不要把优化循环外可以做的事情放到循环里。例如,可以将以下优化加倍:a=range(10000)size_a=len(a)%timeit-n1000foriina:k=len(a)%timeit-n1000foriina:k=size_a1000loops,bestof3:569μsperloop1000loops,bestof3:256μsperloopOptimize多个判断表达式的顺序对于and,满足条件少的放在最前面,对于or,满足条件多的放在前面。如:a=range(2000)%timeit-n100[iforiinaif101900]%timeit-n100[iforiinaifi>1900andi%2==0]100loops,bestof3:287μsperloop100loops,bestof3:214μsperloop100loops,bestof3:128μsperloop100loops,bestof3:56.1μsperloopusingjointomergestringsiniterators1]:%%In[timeit...:s=''...:foriina:...:s+=i...:10000loops,bestof3:59.8μsperloopIn[2]:%%timeits=''.join(a)...:100000loops,bestof3:11.8μsperloopjoin对于累加法,大约有5倍的提升。选择合适的格式字符方法s1,s2='ax','bx'%timeit-n100000'abc%s%s'%(s1,s2)%timeit-n100000'abc{0}{1}'.format(s1,s2)%timeit-n100000'abc'+s1+s2100000loops,bestof3:183nsperloop100000loops,bestof3:169nsperloop100000loops,bestof3:103nsperloop,%方法最慢,但是三者差距不大(都很快)。(个人认为%可读性***)不使用中间变量交换两个变量的值In[3]:%%timeit-n10000a,b=1,2....:c=a;a=b;b=c;....:10000loops,bestof3:172nsperloopIn[4]:%%timeit-n10000a,b=1,2a,b=b,a....:10000loops,bestof3:86nsperloop使用a,b=b,a而不是c=a;a=b;b=c;交换a,b的值,可以快1倍以上。使用ifisa=range(10000)%timeit-n100[iforiinaifi==True]%timeit-n100[iforiinafiisTrue]100loops,bestof3:531μsperloop100loops,bestof3:362μsperloop使用ifisTrue的速度几乎是if==True的两倍。使用级联比较x