分红两部分:代码优化和工具优化html
原文:http://my.oschina.net/xianggao/blog/102600python
阅读 Zen of Python,在Python解析器中输入 import this. 一个犀利的Python新手可能会注意到"解析"一词, 认为Python不过是另外一门脚本语言. "它确定很慢!"linux
毫无疑问:Python程序没有编译型语言高效快速. 甚至Python拥护者们会告诉你Python不适合这些领域. 然而,YouTube已用Python服务于每小时4千万视频的请求. 你所要作的就是编写高效的代码和须要时使用外部实现(C/C++)代码或外部第三方工具.git
代码优化可以让程序运行更快,它是在不改变程序运行结果的状况下使得程序的运行效率更高,根据 80/20 原则,实现程序的重构、优化、扩展以及文档相关的事情一般须要消耗 80% 的工做量。优化一般包含两方面的内容:减少代码的体积,提升代码的运行效率。程序员
有不少方法能够用来缩短程序的执和时间.记住,每当执行一个Python脚本之时,就会调用一个解释器.于是为了对此作补偿,须要对代码作点工做.这与Python是一种解释性语言有很大关系,不过经过减小需加以分析的语句的数量,也会减小解释器的总开销.正则表达式
顺便说起,Python解释器具备一个命令么选项(-0表明optimize--优化),使得程序以不执行某些字节操做的方式加以执行.通常来讲, 该选项用于去除节字码中给出异常产生的行号的注释,并不编译doc字符串和一些其余东西.该标志不会给出太多的速度增益,并且它会使用程序难以调试.算法
取决于如何定义,解释器花费或多或少的时间尝试计算出它们的值.Python在尝试断定变量名时利用动态做用域规则进行处理.当它在代码中找到一个变量时,首先经过查看局部名空间字典考察该变量是否是一个局部变量.若是找到该变量,就抓取该变量的值.不然再在全局名字空间字典中进行搜索,若是须要, 还会搜索内置名字空间.所以,局部变量比其余类型变量的搜索速度要快得多,于是得到它们的值也要快得多.局部变量搜索速度快是由于它们对应于数组中的下标 操做,而全局变量搜索则对应于散列表搜索.一个良好的优化方法是:若是在函数中使用了不少全局变量,把它们的值赋给局部变量可能会有很大帮助.express
在一个脚本之中,只需一次导入一个外部模块便可.所以,在代码中不须要多个import语句.实际上,应该避免在程序中尝试再导入模块.根据以往的经验, 应该把全部的import语句放在程序头的最开始部分.然而,对一个模块屡次调用import不会真正形成问题,由于它只是一个字典查找.若是必需要对一 个外部模块的某些特定属性进行大量引用,开始编写代码以前,应该考虑将这些元素复制到单个变量中(固然,若是可能的话)--特另是若是引用在一个循环内部 进行.只要导入模块,解释器就查找该模块的字节编译版.若是未找到,它会自动对模块进行字节编译并生成.pyc文件.所以,当下次尝试导入此模块时,字节 编译文件就在那里.正如读者所体会的那样.pyc文件比常规.py文件执行起来快不少,由于它们在执行以前就已经经解释器解释过.这里的建议是尽可能使用字 节编译模块.不管是否拥有.pyc文件Python代码都以相同的速度执行.唯一的区别是若是存在.pyc文件,启动将会有所加快.代码的实际运行速度没 有区别.编程
python 中的字符串对象是不可改变的,所以对任何字符串的操做如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串,所以这种持续的 copy 会在必定程度上影响 python 的性能。对字符串的优化也是改善性能的一个重要的方面,特别是在处理文本较多的状况下。windows
1. 在字符串链接的使用尽可能使用 join() 而不是 +;
2. 当对字符串可使用正则表达式或者内置函数来处理的时候,选择内置函数。如 str.isalpha(),str.isdigit(),str.startswith(('x', 'yz')),str.endswith(('x', 'yz'));
3. 对字符进行格式化比直接串联读取要快,所以要在字符串与其余变量链接时就使用格式化字符串.请查看下面的链接形式:
name="Andre"
print "Hello " + name
print "Hello %s" % name
显然与第一个语句相比,第二个print语句更加优化.第三行中的括号是不须要的。
对循环的优化所遵循的原则是尽可能减小循环过程当中的计算量,有多重循环的尽可能将内层的计算提到上一层。
能够在循环中优化大量事件以便它们可平稳运行.下面就是可优化操做的简短清单.在内循环中应该使用内置函数,而不是使用采用Python编写的函数.经过使用运行列表操做的内置函数(例如map(),reduce(),filter())代替直接循环,能够把一些循环开销转移到C代码.向 map,reduce,filter传送内置函数更会使性能得以提升.具备多重循环之时,只有最内层循环值得优化.优化多重循环时,旨在减小内存分配的次数.使最内层循环成为交互做用次数最少者应该有助于性能设计.使用局部变量会大大改善循环内部的处理时间.只要可能,在进入循环前把全部全局变量和属性搜索复制到局部变量.若是在嵌套循环内部使用诸如range(n)之类的结构方法,则在最外层循环外部把值域分配到一个局部变量并在循环定义中使用该变量将快速得多.
yRange=range(500) #优化1
for xItem in range(100000):
for yItem in yRange:
print xItem,yItem
这里的另外一种优化是使用xrange做为循环的x,由于100000项列表是一个至关大的列表.
yRange=range(500)
for xItem in xRange(100000): #优化2
for yItem in yRange:
print xItem,yItem
Python的内置函数比采用纯Python语言编写的函数执行速度要快,由于内置函数是采用C语言编写的.map(),filter()以及 reduce就是在性能上优于采用Python编写的函数的内置函数范例.还应了解,Python把函数名做为全局常数加以处理.既然如此,前面咱们看到 的名字空间搜索的整个概念一样适用于函数.若是能够选择的话,使用map()函数的隐含循环代替for循环要快得多.我在这里提到的循环的执行时间在很大 程序上取决于传送了什么函数.传送Python函数没有传送内置函数(诸如在operator模块里的那些函数)那么快.
在Python中函数调用代价仍是很大的。在计算密集的地方,很大次数的循环体中要尽可能减小函数的调用及调用层次(能inline最好inline)。
一个良好的算法可以对性能起到关键做用,所以性能改进的首要点是对算法的改进。在算法的时间复杂度排序上依次是:
O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)
所以若是可以在时间复杂度上对算法进行必定的改进,对性能的提升不言而喻。
你能够用Python写出高效的代码,但很难击败内建函数. 经查证. 他们很是快速.
你可使用 "+" 来链接字符串. 但因为string在Python中是不可变的,每个"+"操做都会建立一个新的字符串并复制旧内容. 常见用法是使用Python的数组模块单个的修改字符;当完成的时候,使用 join() 函数建立最终字符串.
>>> #This is good to glue a large number of strings
>>> for chunk in input():
>>> my_string.join(chunk)
在Python中即优雅又快速:
>>> x, y = y, x
这样很慢:
>>> temp = x
>>> x = y
>>> y = temp
Python 检索局部变量比检索全局变量快. 这意味着,避免 "global" 关键字.
使用 "in" 关键字. 简洁而快速.
>>> for key in sequence:
>>> print “found”
將 "import" 声明移入函数中,仅在须要的时候导入. 换句话说,若是某些模块不需立刻使用,稍后导入他们. 例如,你没必要在一开使就导入大量模块而加速程序启动. 该技术不能提升总体性能. 但它能够帮助你更均衡的分配模块的加载时间.
有时候在程序中你需一个无限循环.(例如一个监听套接字的实例) 尽管 "while true" 能完成一样的事, 但 "while 1" 是单步运算. 这招能提升你的Python性能.
Python 中条件表达式是 lazy evaluation 的,也就是说若是存在条件表达式 if x and y,在 x 为 false 的状况下 y 表达式的值将再也不计算。所以能够利用该特性在必定程度上提升程序效率。
从Python 2.0 开始,你可使用 list comprehension 取代大量的 "for" 和 "while" 块. 使用List comprehension一般更快,Python解析器能在循环中发现它是一个可预测的模式而被优化.额外好处是,list comprehension更具可读性(函数式编程),并在大多数状况下,它能够节省一个额外的计数变量。列表解析要比在循环中从新构建一个新的 list 更为高效,所以咱们能够利用这一特性来提升运行的效率。例如,让咱们计算1到10之间的偶数个数:
>>> # the good way to iterate a range
>>> evens = [ i for i in range(10) if i%2 == 0]
>>> [0, 2, 4, 6, 8]
>>> # the following is not so Pythonic
>>> i = 0
>>> evens = []
>>> while i < 10:
>>> if i %2 == 0: evens.append(i)
>>> i += 1
>>> [0, 2, 4, 6, 8]
生成器表达式则是在 2.4 中引入的新内容,语法和列表解析相似,可是在大数据量处理时,生成器表达式的优点较为明显,它并不建立一个列表,只是返回一个生成器,所以效率较高。例如:代码 a = [w for w in list] 修改成 a = (w for w in list),运行时间进一步减小,缩短约为 2.98s。
大多数的Python程序员都知道且使用过列表推导(list comprehensions)。若是你对list comprehensions概念不是很熟悉——一个list comprehension就是一个更简短、简洁的建立一个list的方法。
>>> some_list = [1, 2, 3, 4, 5]
>>> another_list = [x + 1 for x in some_list]
>>> another_list
>>> [2, 3, 4, 5, 6]
自从python 3.1 (甚至是Python 2.7)起,咱们能够用一样的语法来建立集合和字典表:
>>>#Se Comprehensions
>>> some_list = [1, 2, 3, 4, 5, 2, 5, 1, 4, 8]
>>> even_set = { x for x in some_list if x % 2 == 0 }
>>> even_set set([8, 2, 4])
>>> # Dict Comprehensions
>>> d = {x: x % 2 == 0 for x in range(1, 11)}
>>> d {1: False, 2: True, 3: False, 4: True, 5: False, 6: True, 7: False, 8: True, 9: False, 10: True}
在第一个例子里,咱们以some_list为基础,建立了一个具备不重复元素的集合,并且集合里只包含偶数。而在字典表的例子里,咱们建立了一个key是不重复的1到10之间的整数,value是布尔型,用来指示key是不是偶数。
这里另一个值得注意的事情是集合的字面量表示法。咱们能够简单的用这种方法建立一个集合:
>>> my_set ={1, 2, 1, 2, 3, 4}
>>> my_set
set([1, 2, 3, 4])
而不须要使用内置函数set()。
set 的 union, intersection,difference 操做要比 list 的迭代要快。所以若是涉及到求 list 交集,并集或者差的问题能够转换为 set 来操做。
set(list1) | set(list2) |
union |
包含list1和list2全部数据的新集合 |
set(list1) & set(list2) |
intersection |
包含list1和list2中共同元素的新集合 |
set(list1) - set(list2) |
difference |
在list1中出现但不在list2中出现的元素的集合 |
Python dict中使用了 hash table,所以查找操做的复杂度为 O(1),所以对成员的查找访问等操做字典要比 list 更快。
检查一个元素是在dicitonary或set是否存在,这在Python中很是快的。这是由于dict和set使用哈希表来实现,查找效率能够达到O(1),而 list 实际是个数组,在 list 中,查找须要遍历整个 list,其复杂度为 O(n),所以,若是您须要常常检查成员,使用 set 或 dict作为你的容器。
>>> mylist = ['a', 'b', 'c'] #Slower, check membership with list:
>>> ‘c’ in mylist
>>> True
>>> myset = set(['a', 'b', 'c']) # Faster, check membership with set:
>>> ‘c’ in myset:
>>> True
这听起来显而易见,但常常被人忘记。对于大多数程序员来讲,数一个东西是一项很常见的任务,并且在大多数状况下并非颇有挑战性的事情——这里有几种方法能更简单的完成这种任务。
Python的collections类库里有个内置的dict类的子类,是专门来干这种事情的:
>>>from collections import Counter
>>>c = Counter('hello world')
>>>c
Counter({'l': 3, 'o': 2, ' ': 1, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})
>>>c.most_common(2)
[('l', 3), ('o', 2)]
这样可为你节省大量的系统内存,由于xrange()在序列中每次调用只产生一个整数元素。而相反 range(),它將直接给你一个完整的元素列表,用于循环时会有没必要要的开销。
这也能够节省内存和提升性能。例如一个视频流,你能够一个一个字节块的发送,而不是整个流。例如:
>>> chunk = ( 1000 * i for i in xrange(1000))
>>> chunk
>>> chunk.next()
0
>>> chunk.next()
1000
>>> chunk.next()
2000
该模块对迭代和组合是很是有效的。让咱们生成一个列表[1,2,3]的全部排列组合,仅需三行Python代码:
>>> import itertools
>>> iter = itertools.permutations([1,2,3])
>>> list(iter)
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
这是一个免费的二分查找实现和快速插入有序序列的工具。也就是说,你可使用:
>>> import bisect
>>> bisect.insort(list, element)
你已將一个元素插入列表中, 而你不须要再次调用 sort() 来保持容器的排序, 由于这在长序列中这会很是昂贵.
Python中的列表实现并非以人们一般谈论的计算机科学中的普通单链表实现的。Python中的列表是一个数组。也就是说,你能够以常量时间O(1) 检索列表的某个元素,而不须要从头开始搜索。这有什么意义呢? Python开发人员使用列表对象insert()时, 需三思.
例如:>>> list.insert(0,item)
在列表的前面插入一个元素效率不高, 由于列表中的全部后续下标不得不改变. 然而,您可使用list.append()在列表的尾端有效添加元素. 优先选择deque,若是你想快速的在插入或删除时。它是快速的,由于在Python中的deque用双链表实现。
原生的list.sort()函数是很是快的。 Python会按天然顺序排序列表。有时,你须要非天然顺序的排序。例如,你要根据服务器位置排序的IP地址。 Python支持自定义的比较,你可使用list.sort(CMP()),这会比list.sort()慢,由于增长了函数调用的开销。若是性能有问 题,你能够申请Guttman-Rosler Transform,基于Schwartzian Transform. 它只对实际的要用的算法有兴趣,它的简要工做原理是,你能够变换列表,并调用Python内置list.sort() - > 更快,而无需使用list.sort(CMP() )->慢。
“@”符号是Python的装饰语法。它不仅用于追查,锁或日志。你能够装饰一个Python函数,记住调用结果供后续使用。这种技术被称为memoization的。下面是一个例子:
>>> from functools import wraps
>>> def memo(f):
>>> cache = { }
>>> @wraps(f)
>>> def wrap(*arg):
>>> if arg not in cache: cache['arg'] = f(*arg)
>>> return cache['arg']
>>> return wrap
咱们也能够对 Fibonacci 函数使用装饰器:
>>> @memo
>>> def fib(i):
>>> if i < 2: return 1
>>> return fib(i-1) + fib(i-2)
这里的关键思想是:加强函数(装饰)函数,记住每一个已经计算的Fibonacci值;若是它们在缓存中,就不须要再计算了.
GIL是必要的,由于CPython的内存管理是非线程安全的。你不能简单地建立多个线程,并但愿Python能在多核心的机器上运行得更快。这是由于 GIL將会防止多个原生线程同时执行Python字节码。换句话说,GIL將序列化您的全部线程。然而,您可使用线程管理多个派生进程加速程序,这些程 序独立的运行于你的Python代码外。
由于GIL会序列化线程, Python中的多线程不能在多核机器和集群中加速. 所以Python提供了multiprocessing模块, 能够派生额外的进程代替线程, 跳出GIL的限制. 此外, 你也能够在外部C代码中结合该建议, 使得程序更快.
注意, 进程的开销一般比线程昂贵, 由于线程自动共享内存地址空间和文件描述符. 意味着, 建立进程比建立线程会花费更多, 也可能花费更多内存. 这点在你计算使用多处理器时要牢记.
好了, 如今你决定为了性能使用本地代码. 在标准的ctypes模块中, 你能够直接加载已编程的二进制库(.dll 或 .so文件)到Python中, 无需担忧编写C/C++代码或构建依赖. 例如, 咱们能够写个程序加载libc来生成随机数。
然而, 绑定ctypes的开销是非轻量级的. 你能够认为ctypes是一个粘合操做系库函数或者硬件设备驱动的胶水. 有几个如 SWIG, Cython和Boost 此类Python直接植入的库的调用比ctypes开销要低. Python支持面向对象特性, 如类和继承. 正如咱们看到的例子, 咱们能够保留常规的C++代码, 稍后导入. 这里的主要工做是编写一个包装器 (行 10~18).
Python有些模块为了性能使用C实现。当性能相当重要而官方文档不足时,能够自由探索源代码。你能够找到底层的数据结构和算法。
1. 若是须要交换两个变量的值使用 a,b=b,a 而不是借助中间变量 t=a;a=b;b=t;
>>> from timeit import Timer
>>> Timer("t=a;a=b;b=t","a=1;b=2").timeit()
0.25154118749729365
>>> Timer("a,b=b,a","a=1;b=2").timeit()
0.17156677734181258
>>>
2. 在循环的时候使用 xrange 而不是 range;使用 xrange 能够节省大量的系统内存,由于 xrange() 在序列中每次调用只产生一个整数元素。而 range() 將直接返回完整的元素列表,用于循环时会有没必要要的开销。在 python3 中 xrange 再也不存在,里面 range 提供一个能够遍历任意长度的范围的 iterator。
3. 使用局部变量,避免"global" 关键字。python 访问局部变量会比全局变量要快得多,所以能够利用这一特性提高性能。
4. if done is not None 比语句 if done != None 更快,读者能够自行验证;
5. 在耗时较多的循环中,能够把函数的调用改成内联的方式;
6. 使用级联比较 "x < y < z" 而不是 "x < y and y < z";
7. while 1 要比 while True 更快(固然后者的可读性更好);
8. build in 函数一般较快,add(a,b) 要优于 a+b。
这些不能替代大脑思考. 打开引擎盖充分了解是开发者的职责,使得他们不会快速拼凑出一个垃圾设计. 以上的Python建议能够帮助你得到好的性能. 若是速度还不够快, Python將须要借助外力:分析和运行外部代码。
有益的提醒,静态编译的代码仍然重要. 仅例举几例, Chrome,Firefox,MySQL,MS Office 和 Photoshop都是高度优化的软件,咱们天天都在使用. Python做为解析语言,很明显不适合. 不能单靠Python来知足那些性能是首要指示的领域. 这就是为何Python支持让你接触底层裸机基础设施的缘由, 将更繁重的工做代理给更快的语言如C. 这高性能计算和嵌入式编程中是关键的功能.
调优给你的代码增长复杂性. 集成其它语言以前, 请检查下面的列表. 若是你的算法是"足够好", 优化就没那么迫切了.
1. 你作了性能测试报告吗?
2. 你能减小硬盘的 I/O 访问吗?
3. 你能减小网络 I/O 访问吗?
4. 你能升级硬件吗?
5. 你是为其它开发者编译库吗?
6. 你的第三方库软件是最新版吗?
对代码优化的前提是须要了解性能瓶颈在什么地方,程序运行的主要时间是消耗在哪里,对于比较复杂的代码能够借助一些工具来定位,python内置了丰富的性能分析工具,如 profile,cProfile 与 hotshot 等。其中 Profiler是python 自带的一组程序,可以描述程序运行时候的性能,并提供各类统计帮助用户定位程序的性能瓶颈。Python 标准模块提供三种 profilers:cProfile,profile 以及 hotshot。
速度的问题可能很微妙, 因此不要依赖于直觉. 感谢 "cprofiles" 模块, 经过简单的运行你就能够监控Python代码.
1. “python -m cProfile myprogram.py”
2. 使用import profile模块
import profile
def profileTest():
Total =1;
for i in range(10):
Total=Total*(i+1)
print Total
return Total
if __name__ == "__main__":
profile.run("profileTest()")
程序的运行结果以下:
其中输出每列的具体解释以下:
ncalls:表示函数调用的次数;
tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
percall:(第一个 percall)等于 tottime/ncalls;
cumtime:表示该函数及其全部子函数的调用运行的时间,即函数开始调用到返回的时间;
percall:(第二个 percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
filename:lineno(function):每一个函数调用的具体信息;
若是须要将输出以日志的形式保存,只须要在调用的时候加入另一个参数。如 profile.run("profileTest()","testprof")。
对于大型应用程序,若是可以将性能分析的结果以图形的方式呈现,将会很是实用和直观,常见的可视化工具备 Gprof2Dot,visualpytune,KCacheGrind 等,读者能够自行查阅相关官网。
控制之后, 提供一个基本的算法性能分析. 恒定时间是理想值. 对数时间复度是稳定的. 阶乘复杂度很难扩展.
O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)
Python 性能优化除了改进算法,选用合适的数据结构以外,还有几种关键的技术,好比将关键 python 代码部分重写成 C 扩展模块,或者选用在性能上更为优化的解释器等,这些在本文中统称为优化工具。python 有不少自带的优化工具,如 Psyco,Pypy,Cython,Pyrex 等,这些优化工具各有千秋。
Psyco
psyco 是一个 just-in-time 的编译器,它可以在不改变源代码的状况下提升必定的性能,Psyco 将操做编译成有点优化的机器码,其操做分红三个不一样的级别,有"运行时"、"编译时"和"虚拟时"变量。并根据须要提升和下降变量的级别。运行时变量只是 常规 Python 解释器处理的原始字节码和对象结构。一旦 Psyco 将操做编译成机器码,那么编译时变量就会在机器寄存器和可直接访问的内存位置中表示。同时 python 能高速缓存已编译的机器码以备从此重用,这样能节省一点时间。但 Psyco 也有其缺点,其自己运行所占内存较大。目前 psyco 已经不在 python2.7 中支持,并且再也不提供维护和更新了,对其感兴趣的能够参考:
http://developer.51cto.com/art/201301/376509.htm
Pypy
PyPy 表示 "用 Python 实现的 Python",但实际上它是使用一个称为RPython的 Python 子集实现的,可以将 Python 代码转成 C, .NET, Java 等语言和平台的代码。PyPy 集成了一种即时 (JIT) 编译器。和许多编译器,解释器不一样,它不关心 Python代码的词法分析和语法树。 由于它是用 Python 语言写的,因此它直接利用 Python 语言的 Code Object。Code Object 是 Python 字节码的表示,也就是说, PyPy直接分析 Python 代码所对应的字节码,这些字节码即不是以字符形式也不是以某种二进制格式保存在文件中,而在 Python 运行环境中。目前版本是 1.8. 支持不一样的平台安装,windows 上安装 Pypy 须要先下载 https://bitbucket.org/pypy/pypy/downloads/pypy-1.8-win32.zip,而后解压到相关的目录,并将解压后的路径添加到环境变量 path 中便可。在命令行运行 pypy,若是出现以下错误:"没有找到 MSVCR100.dll, 所以这个应用程序未能启动,从新安装应用程序可能会修复此问题",则还须要在微软的官网上下载 VS 2010 runtime libraries 解决该问题。具体地址为http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=5555
Cython
Cython 是用 python 实现的一种语言,能够用来写 python 扩展,用它写出来的库均可以经过 import 来载入,性能上比 python 的快。cython 里能够载入 python 扩展 ( 好比import math),也能够载入 c 的库的头文件 ( 好比 :cdef extern from "math.h"),另外也能够用它来写 python 代码。将关键部分重写成 C 扩展模块。
以上第三方工具可参考:
http://www.linuxidc.com/Linux/2012-07/66757p3.htm
但愿这些Python建议能让你成为一个更好的开发者. 最后, 我须要指出, 追求性能极限是一个有趣的游戏, 而过分优化就会变成嘲弄了. 虽然Python授予你与C接口无缝集成的能力, 你必须问本身你花数小时的艰辛优化工做用户是否买账. 另外一方面, 牺牲代码的可维护性换取几毫秒的提高是否值得. 团队中的成员经常会感谢你编写了简洁的代码. 尽可能贴近Python的方式, 由于人生苦短. :)