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算法

Python是解釋型語言,所以它的執行效率不高 [1] ,但這並不影響它的流行。爲了儘量地提高它的執行效率,各類語言自己和藉助於其餘工具等等的方法被人們挖掘出來。這類技巧比較分散,以前被人問起,忽然發現不知從何處講起。今天在網上看到了這篇對提高Python性能的技巧的總結,轉載在這裏。編程

第一部分數組

閱讀 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"

    /media/note/2013/10/02/python-performance-tips/fig2.jpeg

    有時候在程序中你需一個無限循環.(例如一個監聽套接字的實例) 儘管 "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. 使用 dictset 測試成員:


    檢查一個元素是在dicitonary或set是否存在,這在Python中很是快的。這是由於 dictset 使用哈希表來實現。查找效率能夠達到O(1)。所以,若是您須要常常檢查成員,使用 setdict 作爲你的容器.

    >>> 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 模塊也能夠在運行時導入. 這對於檢查長時間運行的進程很是有用.

    import time
    
    def fast():
        time.sleep(0.001)
    def slow():
        time.sleep(0.01)
    def very_slow():
        for i in xrange(100):
            fast()
            slow()
        time.sleep(0.1)
    
    def main():
        very_slow()
        very_slow()
    
    if __name__ == '__main__':
        main()
  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來生成隨機數.

    from ctypes import *
    cdll.LoadLibrary("libc.so.6")
    libc = CDLL("libc.so.6")
    for i in range(10):
        print libc.random() % 10,

    然而, 綁定 ctypes 的開銷是非輕量級的. 你能夠認爲 ctypes 是一個粘合操做系庫函數或者硬件設備驅動的膠水. 有幾個如 SWIG, Cython和Boost此類Python直接植入的庫的調用比 ctypes 開銷要低. Python支持面向對象特性, 如類和繼承. 正如咱們看到的例子, 咱們能夠保留常規的C++代碼, 稍後導入. 這裏的主要工做是編寫一個包裝器 (行 10~18).

    /* First write a struct */
    struct Hello {
        std::string say_hi()
        {
            return "Hello World!";
        }
    }
    
    /* Wrap the Hello struct for Python */
    #include <boost/python.hpp>
    using namespace boost::python;
    
    BOOST_PYTHON_MODULE(mymodule)
    {
        class_<Hello>("Hello")
            .def("say_hi", &Hello::say_hi)
        ;
    }    

         

/* Ready to use in Python */
>>> import mymodule
>>> hello = mymodule.Hello()
>>> hello.say_hi()
'Hello World!'

總結:

我但願這些Python建議能讓你成爲一個更好的開發者. 最後, 我須要指出, 追求性能極限是一個有趣的遊戲, 而過分優化就會變成嘲弄了. 雖然Python授予你與C接口無縫集成的能力, 你必須問本身你花數小時的艱辛優化工做用戶是否買賬. 另外一方面, 犧牲代碼的可維護性換取幾毫秒的提高是否值得. 團隊中的成員經常會感謝你編寫了簡潔的代碼. 儘可能貼近Python的方式, 由於人生苦短. :)

英文原文:

[1] The Computer Language Benchmarks Game

http://www.oschina.net/question/1579_45822

http://www.yeolar.com/note/2013/10/02/python-performance-tips/

相關文章
相關標籤/搜索