需要多数据成员进行频繁的查找或者访问的时候
1 | from time import time |
上述代码运行大概需要 16.09seconds。如果去掉行 #list = dict.fromkeys(list,True) 的注释,将 list 转换为字典之后再运行,时间大约为 8.375 seconds,效率大概提高了一半。因此在需要多数据成员进行频繁的查找或者访问的时候,使用 dict 而不是 list 是一个较好的选择。
list 交集,并集
1 | from time import time |
而是用set求交集
1 | from time import time |
对列表/循环的优化
地下这段代码大概运行时间是132.375
1 | from time import time |
现在进行如下优化,将长度计算提到循环外,range 用 xrange 代替,同时将第三层的计算 lista[a] 提到循环的第二层。
1 | from time import time |
上述优化后的程序其运行时间缩短为 102.171999931
xrange的用法与range相同,即xrange(start, stop, step)根据start与stop指定的范围以及step设定的步长,他所不同的是xrange并不是生成序列,而是作为一个生成器。即他的数据生成一个取出一个。
当列表特别大时,使用xrange省内存。
使用列表解析(list comprehension)和生成器表达式(generator expression)
列表解析要比在循环中重新构建一个新的 list 更为高效,因此我们可以利用这一特性来提高运行的效率。
1 | from time import time |
列表解析
1 | for i in range (1000000): |
上述代码直接运行大概需要 17s,而改为使用列表解析后 ,运行时间缩短为 9.29s。将近提高了一半。生成器表达式则是在 2.4 中引入的新内容,语法和列表解析类似,但是在大数据量处理时,生成器表达式的优势较为明显,它并不创建一个列表,只是返回一个生成器,因此效率较高。在上述例子上中代码 a = [w for w in list] 修改为 a = (w for w in list),运行时间进一步减少,缩短约为 2.98s。
字符串的优化
python 中的字符串对象是不可改变的,因此对任何字符串的操作如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串,因此这种持续的 copy 会在一定程度上影响 python 的性能。
在字符串连接的使用尽量使用 join() 而不是 +:在底下这段代码中中使用 + 进行字符串连接大概需要 0.125 s,而使用 join 缩短为 0.016s。因此在字符的操作上 join 比 + 要快,因此要尽量使用 join 而不是 +。
1 | from time import time |
对字符进行格式化比直接串联读取要快,因此要使用
out = “%s%s%s%s“ % (head, prologue, query, tail)
而不是 out = ““ + head + prologue + query + tail + ““
Cpython
Cython 是用 python 实现的一种语言,可以用来写 python 扩展,用它写出来的库都可以通过 import 来载入,性能上比 python 的快。
Cython 代码与 python 不同,必须先编译,编译一般需要经过两个阶段,将 pyx 文件编译为 .c 文件,再将 .c 文件编译为 .so 文件。编译有多种方法,自行查阅
简单的性能比较·
底下是cpython
1 | from time import time |
Python 测试代码
1 | from time import time |
从上述对比可以看到使用 Cython 的速度提高了将近 100 多倍。
定位程序性能瓶颈
对代码优化的前提是需要了解性能瓶颈在什么地方,程序运行的主要时间是消耗在哪里,对于比较复杂的代码可以借助一些工具来定位,python 内置了丰富的性能分析工具,如 profile,cProfile 与 hotshot 等。其中 Profiler 是 python 自带的一组程序,能够描述程序运行时候的性能,并提供各种统计帮助用户定位程序的性能瓶颈。Python 标准模块提供三种 profilers:cProfile,profile 以及 hotshot。
profile 的使用非常简单,只需要在使用之前进行 import 即可。具体实例如下:
1 | import profile |
输出结果解析:
ncalls:表示函数调用的次数;
tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
percall:(第一个 percall)等于 tottime/ncalls;
cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间;
percall:(第二个 percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
filename:lineno(function):每个函数调用的具体信息;
其他
1 if done is not None 比语句 if done != None 更快,读者可以自行验证;
2 使用级联比较 “x < y < z” 而不是 “x < y and y < z”;
3 while 1 要比 while True 更快(当然后者的可读性更好);
4 build in 函数通常较快,add(a,b) 要优于 a+b。