Py优化技巧

1.函数返回值缓存

可以把一个非常耗时的函数调用变成O(1)时间复杂度
适用于在处理固定参数的函数被重复调用时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import math 
import time
import random

class Memoized:
def __init__(self, fn):
self.fn = fn
self.results = {}
def __call__(self, *args):
key = ''.join(map(str, args[0]))
try:
return self.results[key]
except KeyError:
self.results[key] = self.fn(*args)
return self.results[key]

@Memoized
def twoParamsMemoized(values, period):
totalSum = 0 for x in range(0, 100):
for v in values:
totalSum = math.pow((math.sqrt(v) * period), 4) + totalSum
return totalSum

2.用字典做查询表

用字典做查询表 > 复杂度 O(1)
列表或链表做查询表 > 复杂度 O(n)

trig_lookup_table = defaultdict(lambda: 0)

3.使用默认参数

默认参数(default argument)可以在函数创建时就确定输入值,而不用在运行阶段才确定输入
这种方法只能用于在运行过程中参数不发生变化的函数和对象

1
2
3
4
5
6
import time import math
def degree_sin(deg):
return math.sin(deg * math.pi / 180.0) * math.cos(deg * math.pi / 180.0)

def degree_sin_opt(deg, factor=math.pi/180.0, sin=math.sin, cos=math.cos):
return sin(deg * factor) * cos(deg * factor)

4.列表生成式与生成器

4.1 列表生成式和for循环

1
2
3
4
5
6
multiples_of_two = [x for x in range(100) if x % 2 == 0]

multiples_of_two = []
for x in range(100):
if x % 2 == 0:
multiples_of_two.append(x)

列表生成式比for循环的性能更好

for循环产生的指令集更长
在for循环里,数值是一个一个增加的,用到三个指令(LOAD_ATTR、LOAD_NAME和CALL_FUNCTION)
列表综合只用了一个简单且已经经过优化的指令(LIST_APPEND)

即使for循环需要做额外操作(会产生副作用),也切记不要肆意将所有的for循环都改成列表综合。
这是因为有时候未经优化的列表综合,可能比for循环消耗的时间更长

4.2 生成器

1
2
3
4
5
my_list = (x**2 for x in range(100)) 
# 你不能这么写 print my_list[1]
# 但是可以这么写
for number in my_list:
print number

生成器只能遍历一次,列表对象可以遍历任意次

综合:
生成器表达式在创建规模较大的列表时更有优势,而创建规模较短的列表时,列表综合表达式更有优势

5.ctypes

5.1 把关键代码写成C语言,编译成一个库,然后导入Python当作模块使用
5.2 加载一个系统库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 生成随机数
import time
import random
from ctypes import cdll

libc = cdll.LoadLibrary('libc.so.6') # Linux系统
#libc = cdll.msvcrt # Windows系统

init = time.clock()
# 花费0.9秒
randoms = [random.randrange(1, 100) for x in xrange(1000000)]
print "Pure python: %s seconds" % (time.clock() - init)

# 花费0.3秒
init = time.clock()
randoms = [(libc.rand() % 100) for x in xrange(1000000)]
print "C version : %s seconds" % (time.clock() - init)

6.字符串连接

字符串不可变, 每次对字符串的连接/替换 都会创建新的字符串

1
2
3
4
5
6
7
# more effect
full_doc = "".join(world_list)

# less effect
document = title + introduction + main_piece + conclusion
# more effect
document = "%s%s%s%s" % (title, introduction, main_piece, conclusion)

7.其他

7.1 成员关系测试
当我们想判断一个值是否在一个列表(list)中时
比如像“a in b”这样的操作,用集合(set) 或字典(dict)(查找时间复杂度是O(1)
可以获得比列表(list)和元组(tuple)更好的性能

7.2 不要重复发明轮子
Python的标准库核心组件大都是用经过优化的C语言写成的。因此不需要你自建,而且你自建的很可能会更慢。像列表、元组、集合和字典这些数据类型, 以及数组(array)、迭代工具(itertools)和队列(collections.deque)这些模 块都推荐使用。
使用内置函数, 如 map(operator.add, list1, list2), 也会比 map(lambda x, y: x+y, list1, list2)更快

7.3 不要忘记了队列
当需要一个固定长度的数组或可变长度的栈(stack)时,列表非常适合。
但是,在处理pop(0)或insert(0, your_list)操作时,可以试试collections. deque,因为它在列表的任何一端都可以快速地完成(O(1))插入和弹出操作

7.4 有时不定义函数更好
调用函数会增加大量的资源消耗
有时候,尤其是在时间密集的循环体中内联函数代码,不调用外部函数,可以更加高效
这个窍门有一个很大的代价,因为它可能会损害代码的可读性和维护便利性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time

def fn(nmbr):
return (nmbr ** nmbr) // (nmbr + 1)

nmbr = 0
init = time.clock()
for i in range(1000):
fn(i)

# 0.013s
print("Total time: %s" % (time.clock() - init))
init = time.clock()
nmbr = 0

for i in range(1000):
nmbr = (nmbr ** nmbr) // (nmbr + 1)

# 0.00041s
print("Total time (inline): %s" % (time.clock() - init))

7.5 尽可能用key函数排序
在对列表进行自定义规则的排序时,不要用比较函数排序,而是 应该尽可能地用key函数排序。
这是因为每个元素只需要调用一次key函数,而在运行过 程中每一项都要调用比较函数好几次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import random import time
# 创建两个随机数组
list1 = [[random.randrange(0, 100), chr(random.randrange(32, 122))] for x in range(100000)]
list2 = [[random.randrange(0, 100), chr(random.randrange(32, 122))] for x in
range(100000)]

# 通过比较函数cmp()对两个数据排序

init = time.clock()
list1.sort(cmp=lambda a, b: cmp(a[1], b[1]))

print "Sort by comp: %s" % (time.clock() - init) # 打印结果0.213434
# 把字符串元素作为词典的键,进行排序
init = time.clock()
list2.sort(key=lambda a: a[1])
print "Sort by key: %s" % (time.clock() - init) # 打印结果0.047623

7.6 1比True好
while 1比while True要好
7.7 多元赋值
多元赋值(a,b = “hellothere”, 123)通常比单独赋值要慢。但是,在进行变量交换时,它比普通方法要快(因为我们不需要 使用临时变量和赋值过程)

1
2
3
4
5
6
7
a = "hello world" 
b = 123 # 这种做更快

a, b = b, a # 相比下面这种方式要快
tmp = a
a = b
b = tmp

7.8 推荐使用链式比较
在比较三个变量时,不要用x<y和y<z,可以用x<y<z。这样更容易阅读(更自然)且运行更快

7.9 用命名元组(namedtuple)替换常规对象
使用常规的类(class)方法创建存储数据的简单对象时,实例中会有一个字典存储属性。这个存储对属性少的对象来说是一种浪费。
如果你需要创建大量的简单对象,会浪费大量内存。这种情况下,你可以用命名元组。这是一个新的tuple子类,可以轻松地构建并优化任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time 
import collections

class Obj(object):
def __init__(self, i):
self.i = i
self.l = []

all = {}
init = time.clock()
for i in range(1000000):
all[i] = Obj(i)

# 打印Regular对象:2.384832
print "Regular Objects: %s" % (time.clock() - init)

Obj = collections.namedtuple('Obj', 'i l')
all = {}
init = time.clock()
for i in range(1000000):
all[i] = Obj(i, [])
# 打印NamedTuples对象:1.272023
print "NamedTuples Objects: %s" % (time.clock() - init)

from 《Python性能分析与优化》

坚持原创技术分享,您的支持将鼓励我继续创作!