看到两篇文章介绍了关于Python性能的一些tips,觉得还是挺有用的,分享转载如下。
英文原文:http://blog.monitis.com/index.php/2012/02/13/python-performance-tips-part-1/
英文原文:http://blog.monitis.com/index.php/2012/03/21/python-performance-tips-part-2/
另外,Python官方网站上也有一篇文章介绍了Python性能的tips,见:http://wiki.python.org/moin/PythonSpeed/PerformanceTips
Python 性能小贴士 (第1部分)
译者: TheLover_Z
你可以在 Python 解释器中输入 import this 来看看 Python 之禅 。一些比较敏锐的读者会注意到我使用了“解释器”这个词,然后就认为 Python 只是另一个脚本语言。“它肯定慢得要死!”
大家对这句话应该没有什么疑问:Python 程序确实没有编译型语言更快或者说更有效率。即使是 Python 的拥护者也会告诉你性能方面并不是 Python 所擅长的。然而,YouTube 已经证明了 Python 有能力处理每小时4000万视频的需求 。如果你追求速度,你需要做的是使用第三方库(C/C++)编写更有效的代码。这里有一些小贴士:
使用内置的函数
你当然可以写一些很有效率的代码,但是你很难超越内置的函数(使用 C 编写)。
内置函数: input() , int() , isinstance() , iter() , open() , ord() , pow() , print() , property() 。它们的速度都很快。
使用 join() 来合并大量字符串
你可以使用“+”来合并字符串。但你要考虑到,在 Python 中,字符串是不可变的,所以说每个“+”操作都需要先创建一个新的字符串然后把旧的字符串的内容拷贝过去。一个很常用的做法是用 Python 的数组模块来修改单独的字符。当你处理完毕以后,使用 join() 来生成最终的字符串。
1 2 3 |
>>> #This is good to glue a large number of strings >>> for chunk in input(): >>> my_string.join(chunk) |
使用多重赋值来交换变量的值
这样做更简洁优雅,速度更快:
1 |
>>> x, y = y, x |
这样要慢一点:
1 2 3 |
>>> temp = x >>> x = y >>> y = temp |
尽可能的使用局部变量
Python 检索局部变量的时候速度更快一些。也就是说,尽量避免全局变量吧。
尽量使用 in
要检查成员信息,使用 in 。它看起来干净并且速度快。
1 2 |
>>> for key in sequence: >>> print “found” |
巧用 import 来提速
将 import 放在函数体内,这样只有必要的时候才会导入模块。换句话来说,有些模块并不是马上就需要被导入的,那就等一会儿再导入它们吧!比如说,你没有必要在一开始就导入一长串的模块,这可能会拖慢你程序的速度。不过这个方法并不能提高程序的整体速度。
如果你需要一个死循环,那么请使用 while 1
有些情况下你想要一个死循环。虽然 while True 可以达到一样的效果,但是 while 1 是一个单跳转操作。这样可以提升你程序的速度。
1 2 3 4 |
>>> while 1: >>> #do stuff, faster with while 1 >>> while True: >>> # do stuff, slower with wile True |
使用列表推导式(list comprehension)
从 Python 2.0 开始,你可以使用列表推导式来替换掉许多的 for 和 while 。列表推导式更快一点是因为它针对 Python 解释器做了优化。并且它更易读(函数式编程),在大多数情况下它可以帮你省下来一个计数变量。比如说,我们来用一行代码数一遍从1到10的偶数:
1 2 3 4 5 6 7 8 9 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] |
对于一个非常长的序列请使用 xrange()
这个东西可以帮你省下好多好多的内存,因为它一次只载入一个元素。相对于 range() 一次性载入整个序列, xrange() 可以帮你省下许多内存。
使用 Python 生成器
这也可以帮你节省内存,提高性能。如果你正在加载视频,那么你可以不需要一下子全部加载完。比如说
1 2 3 4 5 6 7 8 9 |
>>> chunk = ( 1000 * i for i in xrange(1000)) >>> chunk <generator object <genexpr> at 0x7f65d90dcaa0> >>> chunk.next() 0 >>> chunk.next() 1000 >>> chunk.next() 2000 |
使用 itertools 模块
这个模块对于排列组合非常有用。我们来用三行代码生成列表[1, 2, 3]的全排列:
1 2 3 4 |
>>> 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)] |
使用 bisect 模块来保持一个列表有序
它是一个二分查找的实现,并且向一个已经排序的列表作插入操作速度很快。具体实现如下:
1 2 |
>>> import bisect >>> bisect.insort(list, element) |
就这样,你向列表(已经排序过的)添加了一个元素。重要的是 你没有必要再次调用 sort() 来进行排序 。是不是很方便?
理解 Python 的列表其实就是一个数组
Python 中的列表并不是我们通常说到的单链表。Python 中的列表,其实是个数组。也就是说,你可以用索引取得元素,消耗时间为O(1),而不是从头开始搜索。这有什么意义呢?当你对列表使用 insert() 操作的时候需要考虑一下。比如说: >>> list.insert(0, element) 当我们向列表头部添加元素的时候效率并不高,因为所有的元素都需要改变位置。你可以使用 list.append() 向列表尾部添加元素,这样效率比较高。如果你想要在双端都可以快速插入或者删除操作的话你可以考虑双向队列。双向队列速度比较快是因为在 Python 中它是由双向链表实现的。不用多说了吧?
使用字典和集合来检查成员的存在关系
Python 在检查一个元素是不是属于一个字典或者集合的时候,速度还是很快的。因为字典和集合是由哈希表实现的,查找操作的复杂度为O(1)。因此,如果你需要频繁的检查存在关系的话,使用字典或者集合吧。
1 2 3 4 5 6 |
>>> 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 |
使用装饰排序(Schwartzian Transform)
list.sort() 非常非常的快。Python 会将列表以自然顺序返回给你。但是如果你需要以非自然顺序排序的话,比如说,你想要按照地理位置对 IP 地址进行排序。Python 支持自定义排序,比如说你可以 list.sort(cmp()) ,但是这样会很慢,因为你需要调用其它的函数。如果速度对你来说很重要的话,你可以试试 Guttman-Rosler 变换,它是基于 Schwartzian 变换的 。读读它的算法实现还是挺有趣的,然后你就可以知道为什么 list.sort() 快一点, list.sort(cmp()) 要慢一点。
使用装饰器缓存结果
“@”是 Python 装饰器的标志。你可以用装饰器来提醒自己这个结果你待会儿要用到。比如说这个例子:
1 2 3 4 5 6 7 8 |
>>> 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 |
我们可以用这个装饰器来生成一个斐波纳切数列:
1 2 3 4 |
>>> @memo >>> def fib(i): >>> if i < 2: return 1 >>> return fib(i-1) + fib(i-2) |
这个想法的要点很简单:增强(即用装饰器)你的函数,让它们可以记得每个计算过的数,这样你就不需要再重新计算了。
理解 Python GIL
GIL 你很有必要去了解一下。因为 CPython 的内存管理并不是线程安全的。你不能简单的创造多线程然后就希望 Python 可以把它们驾驭的很好。这是因为 GIL 会阻止多个本地线程同时执行 Python 字节码。换句话来说,GIL 会将你所有的线程进行序列化(serialize)。然而,想要给你的程序提速,你可以使用线程来管理数个并行的进程,它们可以在 Python 代码之外独立的运行。
把 Python 源代码当作你的文档
Python 有许多模块为了速度都用 C 来实现。当性能很苛刻并且官方文档不够的时候,看看你自己的代码。你也许可以发现潜在的数据结构和算法。Python 库是个不错的地方:http://svn.python.org/view/python/trunk/Modules
结论
没什么东西可以替代人的大脑。当你觉得这个主意不够好的时候,再想想有没有更好的。希望这篇文章的一些小贴士可以让你的代码获得更高的性能。如果速 度还是没有得到满足的话,Python 就需要外部的帮助了:性能分析,然后使用外部代码。我们将会在第二部分来讲讲这个问题。
第二部分 中文翻译来自:开源中国社区 http://www.oschina.net/question/1579_45822 (本文转载中省略了代码图片等,请到原文阅读,谢谢)
有益的提醒,静态编译的代码仍然重要. 仅例举几例, Chrome,Firefox,MySQL,MS Office 和 Photoshop都是高度优化的软件,我们每天都在使用. Python作为解析语言,很明显不适合. 不能单靠Python来满足那些性能是首要指示的领域. 这就是为什么Python支持让你接触底层裸机基础设施的原因, 将更繁重的工作代理给更快的语言如C. 这高性能计算和嵌入式编程中是关键的功能. Python性能鸡汤第一部分讨论了怎样高效的使用Python. 在第二部分, 我们將涉及监控和扩展Python.
1. 首先, 拒绝调优诱惑
调优给你的代码增加复杂性. 集成其它语言之前, 请检查下面的列表. 如果你的算法是"足够好", 优化就没那么迫切了.
1. 你做了性能测试报告吗?
2. 你能减少硬盘的 I/O 访问吗?
3. 你能减少网络 I/O 访问吗?
4. 你能升级硬件吗?
5. 你是为其它开发者编译库吗?
6.你的第三方库软件是最新版吗?
2. 使用工具监控代码, 而不是直觉
速度的问题可能很微妙, 所以不要依赖于直觉. 感谢 "cprofiles" 模块, 通过简单的运行你就可以监控Python代码
“python -m cProfile myprogram.py”
我们写了个测试程序. 基于黑盒监控. 这里的瓶颈是 "very_slow()" 函数调用. 我们还可以看到 "fast()" 和 "slow()"都被调用200次. 这意味着, 如果我们可以改善 "fast()" 和 "slow()" 函数, 我们可以获得全面的性能提升. cprofiles 模块也可以在运行时导入. 这对于检查长时间运行的进程非常有用.
3. 审查时间复杂度
控制以后, 提供一个基本的算法性能分析. 恒定时间是理想值. 对数时间复度是稳定的. 阶乘复杂度很难扩展.
O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)
4. 使用第三方包
有很多为Python设计的高性能的第三方库和工具. 下面是一些有用的加速包的简短列表.
1. NumPy: 一个开源的相当于MatLab的包
2. SciPy: 另一个数值处理库
3. GPULib: 使用GPUs加速代码
4. PyPy: 使用 just-in-time 编译器优化Python代码
5. Cython: 將Python优码转成C
6. ShedSkin: 將Python代码转成C++
5. 使用multiprocessing模块实现真正的并发
因为GIL会序列化线程, Python中的多线程不能在多核机器和集群中加速. 因此Python提供了multiprocessing模块, 可以派生额外的进程代替线程, 跳出GIL的限制. 此外, 你也可以在外部C代码中结合该建议, 使得程序更快.
注意, 进程的开销通常比线程昂贵, 因为线程自动共享内存地址空间和文件描述符. 意味着, 创建进程比创建线程会花费更多, 也可能花费更多内存. 这点在你计算使用多处理器时要牢记.
6. 本地代码
好了, 现在你决定为了性能使用本地代码. 在标准的ctypes模块中, 你可以直接加载已编程的二进制库(.dll 或 .so文件)到Python中, 无需担心编写C/C++代码或构建依赖. 例如, 我们可以写个程序加载libc来生成随机数.
然而, 绑定ctypes的开销是非轻量级的. 你可以认为ctypes是一个粘合操作系库函数或者硬件设备驱动的胶水. 有几个如 SWIG, Cython和Boost 此类Python直接植入的库的调用比ctypes开销要低. Python支持面向对象特性, 如类和继承. 正如我们看到的例子, 我们可以保留常规的C++代码, 稍后导入. 这里的主要工作是编写一个包装器 (行 10~18).
总结:
我希望这些Python建议能让你成为一个更好的开发者. 最后, 我需要指出, 追求性能极限是一个有趣的游戏, 而过度优化就会变成嘲弄了. 虽然Python授予你与C接口无缝集成的能力, 你必须问自己你花数小时的艰辛优化工作用户是否买帐. 另一方面, 牺牲代码的可维护性换取几毫秒的提升是否值得. 团队中的成员常常会感谢你编写了简洁的代码. 尽量贴近Python的方式, 因为人生苦短.
2 Comments