优化三个原则:
- 1.不要过早的优化,先要让程序实现功能,然后在进行优化
- 2.权衡优化代价,优化是有代价的,通常面临的是时间与空间的交换,以及开发代价的也需要考虑
- 3.不要优化那些无关紧要的部分,专注运行慢的地方优化即可(如内部循环)
加速技巧:
1.避免全局变量
定义在全局范围内的代码运行速度会比定义在函数中的慢很多 ,将全局范围内定义的代码放到函数中速度将带来15%-30%的提升.
2.避免模块和函数属性访问
如
一:
imort math
代码中用math.sqrt()
二:
form math import sqrt
代码中用sqrt()
第一种的话,每次使用’. ’,(属性访问操作符),都会触发特定的操作,如__getattribute__()和__getattr__(),这些操作会进行字典操作,带来额外的时间开销.通过方法二,from 即可消除属性访问操作带来的问题.
三:
import math
def xx():
sqrt = math.sqrt
同时可以进一步优化,可以看出方法二中的sqrt(),是全局变量,因此可以将将其放入函数中变为局部变量,面对频繁访问sqrt的代码段这样的操作可以提速很多,这里值得注意的是,这有个常见的操作就是:
for i in range(n):
list.append (I)
这里也频繁的调用属性访问操作,因此可以将其赋值给局部变量
append = list.append
for i in range(n):
append(i)
3.避免类内属性的访问
同样出现了类内属性的频繁访问的话,比如循环中出现self.value,那么最好将他赋值给局部变量,这样访问速度会快一点.
4.避免不必要的抽象
使用额外的处理层(如装饰器,属性访问,描述器)等去包装代码时,会让代码变得很慢,如
@ property
@ setter
这些都是C++遗留下的代码风格,如没有必要的话就将其简化
4.避免数据无意义的复制
以及避免滥用copy.deepcopy()
5.交换值时避免使用中间变量
如:
temp = a
a = b
b = temp
改进为:
a, b = b, a
6.避免用+进行字符串的拼接,用join()
a+b,由于Python中字符串是不可变对象,其会申请一块内存空间,将a, b分别复制到新申请的内存空间中,因此面对频繁的字符串拼接,如n个字符串的拼接,会产生n-1个中间结果,每个中间结果都需要申请和复制一次内存,严重影响运行效率.而在用join()进行字符串的拼接时,会首先计算出需要的申请的总的内存空间,然后一次性地申请所需内存,并将每个字符串元素复制到该内存中去.
7.利用if条件的短路特性
if a and b: #如果a为true,则不再计算b
if a or b : #如果 a为 Flase 则不再计算 b
因此编程时要预判a b两个条件的为真的概率,根据概率决定谁在and (or) 前,减少没必要的计算.
8.用for 循环代替while循环,for循环比while循环快不少.,同时用隐式for 循环代替显示for循环.
如
显示
def computeSum(size):
sum = 0
for I in range(size) :
sum += 1
return sum
隐式 :
def computeSum(size):
return sum(range(size))
少去了一些中间变量的创建等操作.
9.减少内层for循环的计算
如:
import math
def computeSqrt (m, n):
sqrt = math.sqrt
for x in range (m):
for y in range(n):
z = sqrt(x) + sqrt(y)
看起来合乎逻辑,其实sqrt(x)产生了很多不必要的计算
改进:
import math
def computeSqrt (m, n):
sqrt = math.sqrt
for x in range (m):
x_sqrt = sqrt (x)
for y in range(n):
z = x_sqrt + sqrt(y)
10.使用numba.jit进行加速
numba是一个用于编译Python数组和数值计算函数的编译器,这个编译器能够大幅提高直接使用Python编写的函数的运算速度。
Numba的主要特性:
-
动态代码生成 (在用户偏爱的导入期和运行期)
-
为CPU(默认)和GPU硬件生成原生的代码
-
集成Python的科学软件栈(Numpy)
只需要在函数前加上
@numba.jit装饰器即可(未完待续)
11.选择合适的数据结构
Python中内置的数据结构,如str, tuple,list, set,dict底层都是c实现的,速度已经很快了.其中list,是一种动态数组,其会预分配一定的内存空间,当预分配的内存空间用完之后,又继续向其中添加元素时,会申请一个更大的内存空间,将原来的所有的元素都复制过去,之后销毁原来的内存空间,在插入新的元素.删除元素的操作类似,当已经使用的空间比原先分分配的空间一半还少时,会另外申请一个更小的空间,做一次元素的复制,之后销毁原来的大内存空间.因此如果有频繁的新增,删除操作时,且新增,删除的元素又比较多,这时的list效率就比较低,可以考虑使用,collections.deque.
class collections.deque([iterable[, maxlen]])
返回一个新的双向队列对象,从左到右初始化(用方法 append()) ,从 iterable (迭代对象) 数据创建。如果 iterable 没有指定,新队列为空。
Deque队列是由栈或者queue队列生成的(发音是 “deck”,”double-ended queue”的简称)。Deque 支持线程安全,内存高效添加(append)和弹出(pop),从两端都可以,两个方向的大概开销都是 O(1) 复杂度。
虽然 list 对象也支持类似操作,不过这里优化了定长操作和 pop(0) 和 insert(0, v) 的开销。它们引起 O(n) 内存移动的操作,改变底层数据表达的大小和位置。
如果 maxlen 没有指定或者是 None ,deques 可以增长到任意长度。否则,deque就限定到指定最大长度。一旦限定长度的deque满了,当新项加入时,同样数量的项就从另一端弹出。限定长度deque提供类似Unix filter tail 的功能。它们同样可以用与追踪最近的交换和其他数据池活动。
双向队列(deque)对象支持以下方法:
append(x) 添加 x 到右端。
appendleft(x) 添加 x 到左端。
clear() 移除所有元素,使其长度为0.
copy() 创建一份浅拷贝。
count(x) 计算 deque 中元素等于 x 的个数。
extend(iterable) 扩展deque的右侧,通过添加iterable参数中的元素。
extendleft(iterable) 扩展deque的左侧,通过添加iterable参数中的元素。注意,左添加时,在结果中iterable参数中的顺序将被反过来添加。
index(x[, start[, stop]]) 返回 x 在 deque 中的位置(在索引 start 之后,索引 stop 之前)。 返回第一个匹配项,如果未找到则引发 ValueError。
insert(i, x) 在位置 i 插入 x 。如果插入会导致一个限长 deque 超出长度 maxlen 的话,就引发一个 IndexError。
pop() 移去并且返回一个元素,deque 最右侧的那一个。 如果没有元素的话,就引发一个 IndexError。
popleft() 移去并且返回一个元素,deque 最左侧的那一个。 如果没有元素的话,就引发 IndexError。
remove(value) 移除找到的第一个 value。 如果没有的话就引发 ValueError。
reverse()将deque逆序排列。返回 None 。
rotate(n=1) 向右循环移动 n 步。 如果 n 是负数,就向左循环。如果deque不是空的,向右循环移动一步就等价于 d.appendleft(d.pop()) , 向左循环一步就等价于 d.append(d.popleft()) 。
Deque对象同样提供了一个只读属性:
maxlen Deque的最大尺寸,如果没有限定的话就是 None 。