优化每一个细节
函数返回值缓存/函数查询表
把函数、输入参数、返回值全部缓存下来,在函数下次调用时直接使用存储的结果(不需要重新计算所有数据) 使用情景:在处理固定参数的函数被重复调用时。这样做可以确保每次函数被调用时,直接返回缓存结果。 1、用列表或链表做查询表 O(N) 2、用字典做查询表 O(1) # 只要哈希后的值不重复,就是有效的 3、二分查找 O(logn) # 但是要有序,因此排序也是耗费时间的
默认参数
import math # 原始函数 def degree_sin(deg): return math.sin(deg * math.pi / 180) # 优化的函数,因子变量是在函数创建时建立的 # 所以直接用math.sin方法查询即可 def degree_sin(deg, factor=math.pi/180, sin=math.sin): return sin(deg * factor) # 但这种优化方法,因为在运行过程中,函数预先计算的项目是不能改变的,所以函数的接口容易造成混乱。
列表表达式与生成器
######################### mult_line = [ i for i in range(100) if x % 2 == 0 ] ######################### mult_line = [] for i in range(100): if x % 2 == 0: mult_line.append(i) #########################
搞明白为什么列表表达式比for循环性能更好?
我们查看它们分别解析后的字节码:
而其对应的字节码,我们识别不出来,因此我们使用dis模块把字节码转换成人能读懂的形式,分析它们执行的细节。
import dis import inspect import timeit programs = dict( loop=""" multiples_of_two = [] for x in range(100): if x % 2 == 0: multiples_of_two.append(x)""", comprehension='multiples_of_two = [x for x in range(100) if x % 2 == 0]', ) for name, text in programs.items(): print(name, timeit.Timer(stmt=text).timeit()) code = compile(text, '<string>', 'exec') dis.disassemble(code)
D:\Python35\python.exe "D:/Destop/性能分析/chapter 4/dis_list_comprehension.py" loop 15.3982382 2 0 BUILD_LIST 0 3 STORE_NAME 0 (multiples_of_two) 3 6 SETUP_LOOP 49 (to 58) 9 LOAD_NAME 1 (range) 12 LOAD_CONST 0 (100) 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 18 GET_ITER >> 19 FOR_ITER 35 (to 57) 22 STORE_NAME 2 (x) 4 25 LOAD_NAME 2 (x) 28 LOAD_CONST 1 (2) 31 BINARY_MODULO 32 LOAD_CONST 2 (0) 35 COMPARE_OP 2 (==) 38 POP_JUMP_IF_FALSE 19 5 41 LOAD_NAME 0 (multiples_of_two) 44 LOAD_ATTR 3 (append) 47 LOAD_NAME 2 (x) 50 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 53 POP_TOP 54 JUMP_ABSOLUTE 19 >> 57 POP_BLOCK >> 58 LOAD_CONST 3 (None) 61 RETURN_VALUE comprehension 12.4666929 1 0 LOAD_CONST 0 (<code object <listcomp> at 0x000002823D52F810, file "<string>", line 1>) 3 LOAD_CONST 1 ('<listcomp>') 6 MAKE_FUNCTION 0 9 LOAD_NAME 0 (range) 12 LOAD_CONST 2 (100) 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 18 GET_ITER 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 STORE_NAME 1 (multiples_of_two) 25 LOAD_CONST 3 (None) 28 RETURN_VALUE Process finished with exit code 0
可以看出,for循环产生的指令集更长。在for循环里,数值是一个一个增加的,用到三个指令(LOAD_ATTR, LOAD_NAME 和 CALL_FUNCTION)。
但是,从图中可以看出,列表表达式只用了一个简单且已经经过优化的指令(LIST_APPEND)
但是,即便for循环需要做额外的操作,也切记不要肆意将所有的for循环都改成列表生成式,因为有时候未经优化的列表表达式,
可能比for循环消耗的时间更长
注意:在处理大列表的时候,列表表达式可能就不好使了,因为列表表达式需要直接产生每一个值。因此,
如果你要处理一个包含10万元素的列表还有一种更好的方法。你可以用生成器表达式,不需要直接返回列表,而是返回一个生成器对象,
它会动态地生成列表元素
ctypes
具体可以看另一篇博客

可以让开发者直接进入Python的底层,借助C语言的力量进行开发。这个库只有CPython才有,因为这个版本是C语言写的。 这个连接C语言的接口可以做很多事情,因为你可以直接加载预编译代码,并用C语言执行。也就是说通过它接入Windows系统上的kernel32.dll和msvcrt.dll动态链接库,以及Linux系统的lib.so.6库 详情参考官方文档: https://docs.python.org/2/library/stypes.html ------------check_prime.c-------------------- #include <stdio.h> #include <math.h> int check_prime(int a) { int c; for ( c = 2 ; c <= sqrt(a) ; c++ ) { if ( a%c == 0 ) return 0; } return 1; } ################# gcc -shared -o check_prime.so -fPIC check_prime.c import time import ctypes import math check_primes_types = ctypes.CDLL('./check_primes.so').check_prime def check_prime(x): values = range(2, int(math.sqrt(x))) for i in values: if x % i == 0: return False return True init = time.clock() numbers_py = [x for x in range(1000000) if check_prime(x)] print("Full python version: %s seconds" % (time.clock() - init)) init = time.clock() numbers_c = [x for x in range(1000000) if check_primes_types(x)] print("C version: %s seconds" % (time.clock() - init)) print(len(numbers_py)) ################################################ Full python version: 7.042522 seconds C version: 0.7749299999999995 seconds 78705 然后发现,我的天啊,这能比较么,效率差这么多。。。。。。。。。。。。。。。。

import time import random from ctypes import cdll libc = cdll.LoadLibrary('libc.so.6') # linux systems # libc = cdll.msvcrt #windows systems init = time.clock() randoms = [random.randrange(1, 100) for x in range(1000000)] print("Pure python: %s seconds" % (time.clock() - init)) init = time.clock() randoms = [(libc.rand() % 100) for x in range(1000000)] print("C version : %s seconds" % (time.clock() - init)) ######## c的效率还是那么高
字符串拼接
因为在python内字符串是不可变的,所以每当修改都是创建了一个新的字符串,然后指向这个新的。因此处理字符串都要很小心。 import time import sys option = sys.argv[1] words = [str(x) for x in range(1000000)] if option == '1': full_doc = "" init = time.clock() for w in words: full_doc += w print("Time using for-loop: %s" % (time.clock() - init)) else: init = time.clock() full_doc = "".join(words) print("Time using join: %s" % (time.clock() - init)) ######################################## ace@ace-virtual-machine:$ /usr/bin/time -f 'Memory: %M bytes' python3 string_concat.py 1 Time using for-loop: 0.13839400000000002 Memory: 77724 bytes ace@ace-virtual-machine:$ /usr/bin/time -f 'Memory: %M bytes' python3 string_concat.py 0 Time using join: 0.014417000000000013 Memory: 77524 bytes 结论:join 消耗的内存更少,时间更短 ############################################## 另外当遇到一下情况时: document = title + introduction + main_price + conclusion document = "%s%s%s%s" % (title ,introduction , main_price , conclusion) document = "%(title)s%(introduction)s%(main_price)s%(conclusion)s" % locals() 这三种效率依次增加
其他优化技巧
成员关系测试
a in b b : 列表 O(n) 集合 O(1) 字典 O(1) 元组 O(n)
不要重复发明轮子
python标准库核心组件都是经过优化的,
像 列表,元组,集合,字典,以及数组(array),迭代工具(itertools)和队列(collections.deque)这些模块都推荐使用。
如内置函数
map(operator.add,list1,list2)也会比
map(lamba x,y:x+y, list1, list2) 更快
不要忘记了队列
当需要固定一个长度的数组或可变长度的栈时,列表很合适。但是在处理pop(0)以及insert(0,value)时,可以试试collections.deque,
因为它能在列表的任何一端都能快速的完成O(1)插入和弹出操作
有时不定义函数更好
调用函数会增加大量的资源消耗, 因此,有时候,尤其是在时间密集的循环体体中内联函数代码,不调用外部函数,可以更加高效。
但这有时可能会损害代码的可读性和维护便利性。因此仅当必须提升性能才应该这样做。
尽可能用key函数排序
1 比 true 好
多元赋值
多元赋值比 单元赋值 要慢
但是在变量交换上,比普通方法要快
推荐使用链式比较
比如 x<y<z 比 x<y和y<z 更好
用命名元组(nametuple)替换常规对象
使用常规的类创建对象存储数据的简单对象时,实例中会有一个字典存储属性。这个存储对属性少的对象来说是一种浪费。 如果需要创建大量的简单对象,会浪费大量内存。这种情况下,可以使用命名元组。 这是一个新的tuple子类,可以轻松地构建并优化内存。 https://docs.python.org/2/library/collections.html#collections.namedtuple 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) 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, []) print("Regular Objects: %s" % (time.clock() - init)) ################################################ D:\Python35\python.exe "D:/example.py" Regular Objects: 1.9968633 Regular Objects: 1.5068022