Python性能鸡汤(转)

英文原文:http://blog.monitis.com/index.php/2012/02/13/python-performance-tips-part-1/php

英文原文:http://blog.monitis.com/index.php/2012/03/21/python-performance-tips-part-2/python

翻译原文:http://www.oschina.net/question/1579_45822算法

第一部分

阅读 Zen of Python,在Python解析器中输入 import this. 一个犀利的Python新手可能会注意到"解析"一词, 认为Python不过是另外一门脚本语言. "它确定很慢!"

毫无疑问:Python程序没有编译型语言高效快速. 甚至Python拥护者们会告诉你Python不适合这些领域. 然而,YouTube已用Python服务于每小时4千万视频的请求. 你所要作的就是编写高效的代码和须要时使用外部实现(C/C++)代码. 这里有一些建议,能够帮助你成为一个更好的Python开发者:
编程

1. 使用内建函数:数组

    你能够用Python写出高效的代码,但很难击败内建函数. 经查证. 他们很是快速.缓存


2.使用join()链接字符串.安全

      你可使用 "+" 来链接字符串. 但因为string在Python中是不可变的,每个"+"操做都会建立一个新的字符串并复制旧内容. 常见用法是使用Python的数组模块单个的修改字符;当完成的时候,使用 join() 函数建立最终字符串.
      >>> #This is good to glue a large number of strings
      >>> for chunk in input():
      >>>    my_string.join(chunk)

3. 使用Python多重赋值,交换变量
    这在Python中即优雅又快速:
      >>> x, y = y, x
      这样很慢:
      >>> temp = x
      >>> x = y
      >>> y = temp          

4. 尽可能使用局部变量
    Python 检索局部变量比检索全局变量快. 这意味着,避免 "global" 关键字.

5. 尽可能使用 "in"
      使用 "in" 关键字. 简洁而快速.
      >>> for key in sequence:
      >>>     print “found”

6. 使用延迟加载加速
      將 "import" 声明移入函数中,仅在须要的时候导入. 换句话说,若是某些模块不需立刻使用,稍后导入他们. 例如,你没必要在一开使就导入大量模块而加速程序启动. 该技术不能提升总体性能. 但它能够帮助你更均衡的分配模块的加载时间.

7. 为无限循环使用 "while 1"
    有时候在程序中你需一个无限循环.(例如一个监听套接字的实例) 尽管 "while true" 能完成一样的事, 但 "while 1" 是单步运算. 这招能提升你的Python性能.
       >>> while 1:
      >>>    #do stuff, faster with while 1
      >>> while True: 服务器

      >>>    # do stuff, slower with wile True 网络


8. 使用list comprehension
    从Python 2.0 开始,你可使用 list comprehension 取代大量的 "for" 和 "while" 块. 使用List comprehension一般更快,Python解析器能在循环中发现它是一个可预测的模式而被优化.额外好处是,list comprehension更具可读性(函数式编程),并在大多数状况下,它能够节省一个额外的计数变量。例如,让咱们计算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]

9. 使用xrange()处理长序列:
    这样可为你节省大量的系统内存,由于xrange()在序列中每次调用只产生一个整数元素。而相反 range(),它將直接给你一个完整的元素列表,用于循环时会有没必要要的开销。

10. 使用 Python generator:
      这也能够节省内存和提升性能。例如一个视频流,你能够一个一个字节块的发送,而不是整个流。例如,     
      >>> chunk = ( 1000 * i for i in xrange(1000))
      >>> chunk
      <generator object <genexpr> at 0x7f65d90dcaa0>
      >>> chunk.next()
      0
      >>> chunk.next()
      1000
      >>> chunk.next()
      2000

11. 了解itertools模块:
      该模块对迭代和组合是很是有效的。让咱们生成一个列表[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)]

12. 学习bisect模块保持列表排序:
      这是一个免费的二分查找实现和快速插入有序序列的工具。也就是说,你可使用:
      >>> import bisect
      >>> bisect.insort(list, element)
      你已將一个元素插入列表中, 而你不须要再次调用 sort() 来保持容器的排序, 由于这在长序列中这会很是昂贵.

13. 理解Python列表,其实是一个数组:
      Python中的列表实现并非以人们一般谈论的计算机科学中的普通单链表实现的。Python中的列表是一个数组。也就是说,你能够以常量时间O(1) 检索列表的某个元素,而不须要从头开始搜索。这有什么意义呢? Python开发人员使用列表对象insert()时, 需三思. 例如:>>> list.insert(0,item)
      在列表的前面插入一个元素效率不高, 由于列表中的全部后续下标不得不改变. 然而,您可使用list.append()在列表的尾端有效添加元素. 挑先deque,若是你想快速的在两插入或时。它是快速的,由于在Python中的deque用双链表实现。再也不多说。 :)
数据结构

14. 使用dict 和 set 测试成员:

      检查一个元素是在dicitonary或set是否存在 这在Python中很是快的。这是由于dict和set使用哈希表来实现。查找效率能够达到O(1)。所以,若是您须要常常检查成员,使用 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

15. 使用Schwartzian Transform 的 sort():


      原生的list.sort()函数是很是快的。 Python会按天然顺序排序列表。有时,你须要非天然顺序的排序。例如,你要根据服务器位置排序的IP地址。 Python支持自定义的比较,你可使用list.sort(CMP()),这会比list.sort()慢,由于增长了函数调用的开销。若是性能有问 题,你能够申请Guttman-Rosler Transform,基于Schwartzian Transform. 它只对实际的要用的算法有兴趣,它的简要工做原理是,你能够变换列表,并调用Python内置list.sort() - > 更快,而无需使用list.sort(CMP() )->慢。


16. Python装饰器缓存结果:
      “@”符号是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值;若是它们在缓存中,就不须要再计算了.

17. 理解Python的GIL(全局解释器锁):
      GIL是必要的,由于CPython的内存管理是非线程安全的。你不能简单地建立多个线程,并但愿Python能在多核心的机器上运行得更快。这是由于 GIL將会防止多个原生线程同时执行Python字节码。换句话说,GIL將序列化您的全部线程。然而,您可使用线程管理多个派生进程加速程序,这些程 序独立的运行于你的Python代码外。

18. 像熟悉文档同样的熟悉Python源代码:
      Python有些模块为了性能使用C实现。当性能相当重要而官方文档不足时,能够自由探索源代码。你能够找到底层的数据结构和算法。 Python的源码库就是一个很棒的地方:http://svn.python.org/view/python/trunk/Modules

结论:
这些不能替代大脑思考. 打开引擎盖充分了解是开发者的职责,使得他们不会快速拼凑出一个垃圾设计. 本文的Python建议能够帮助你得到好的性能. 若是速度还不够快, Python將须要借助外力:分析和运行外部代码.咱们將在本文的第二部分中涉及.

第二部分

有益的提醒,静态编译的代码仍然重要. 仅例举几例, 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的方式, 由于人生苦短. :)

英文原文: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/

相关文章
相关标签/搜索