代碼優化指南:人生苦短,我用Python

選自pythonfilesphp

機器之心編譯html

參與:Panda前端

前段時間,Python Files 博客發佈了幾篇主題爲「Hunting Performance in Python Code」的系列文章,對提高 Python 代碼的性能的方法進行了介紹。在其中的每一篇文章中,做者都會介紹幾種可用於 Python 代碼的工具和分析器,以及它們能夠如何幫助你更好地在前端(Python 腳本)和/或後端(Python 解釋器)中找到瓶頸。機器之心對這個系列文章進行了整理編輯,將其融合成了這一篇深度長文。本文的相關代碼都已經發布在 GitHub 上。

代碼地址:https://github.com/apatrascu/hunting-python-performancepython


第一部分請查看從環境設置到內存分析。如下是 Python 代碼優化的第二部分,主要從 Python 腳本與 Python 解釋器兩個方面闡述。在這一部分中咱們首先會關注如何追蹤 Python 腳本的 CPU 使用狀況,並重點討論 cProfile、line_profiler、pprofile 和 vprof。然後一部分重點介紹了一些可用於在運行 Python 腳本時對解釋器進行性能分析的工具和方法,主要討論了 CPython 和 PyPy 等。git


CPU 分析——Python 腳本github

在這一節,我將介紹一些有助於咱們解決 Python 中分析 CPU 使用的難題的工具。後端

CPU 性能分析(profiling)的意思是經過分析 CPU 執行代碼的方式來分析這些代碼的性能。也就是說要找到咱們代碼中的熱點(hot spot),而後看咱們能夠怎麼處理它們。瀏覽器

接下來咱們會看看你能夠如何追蹤你的 Python 腳本的 CPU 使用。咱們將關注如下分析器(profiler):app

  • cProfile
  • line_profiler
  • pprofile
  • vprof


測量 CPU 使用wordpress

這一節我將使用與前一節基本同樣的腳本,你也能夠在 GitHub 上查看:https://gist.github.com/apatrascu/8524679175de08a54a95e22001a31d3b

另外,記住在 PyPy2 上,你須要使用一個支持它的 pip 版本:

其它東西能夠經過如下指令安裝:

cProfile

在 CPU 性能分析上最經常使用的一個工具是 cProfile,主要是由於它內置於 CPython2 和 PyPy2 中。這是一個肯定性的分析器,也就是說它會在運行咱們的負載時收集一系列統計數據,好比代碼各個部分的執行次數或執行時間。此外,相比於其它內置的分析器(profile 或 hotshot),cProfile 對系統的開銷更少。

當使用 CPython2 時,其使用方法是至關簡單的:

若是你使用的是 PyPy2:

其輸出以下:

即便是這樣的文本輸出,咱們也能夠直接看到咱們腳本的大多數時間都在調用 list.append 方法。

若是咱們使用 gprof2dot,咱們能夠用圖形化的方式來查看 cProfile 的輸出。要使用這個工具,咱們首先必須安裝 graphviz。在 Ubuntu 上,可使用如下命令:

再次運行咱們的腳本:

而後咱們會獲得下面的 output.png 文件:

這樣看起來就輕鬆多了。讓咱們仔細看看它輸出了什麼。你能夠看到來自腳本的函數調用圖(callgraph)。在每一個方框中,你能夠一行一行地看到:

  • 第一行:Python 文件名、行數和方法名
  • 第二行:這個方框所用的時間佔全局時間的比例
  • 第三行:括號中是該方法自己所用時間佔全局時間的比例
  • 第四行:調用次數

好比說,在從上到下第三個紅色框中,方法 primes 佔用了 98.28% 的時間,65.44% 的時間是在該方法之中作什麼事情,它被調用了 40 次。剩下的時間被用在了 Python 的 list.append(22.33%)和 range(11.51%)方法中。

這是一個簡單的腳本,因此咱們只須要重寫咱們的腳本,讓它不用使用那麼多的 append 方法,結果以下:

如下測試了腳本在使用前和使用 CPython2 後的運行時間:

用 PyPy2 測量:

咱們在 CPython2 上獲得了 2.4 倍的提高,在 PyPy2 上獲得了 3.1 倍的提高。很不錯,其 cProfile 調用圖爲:

你也能夠以程序的方式查看 cProfile:

這在一些場景中頗有用,好比多進程性能測量。更多詳情請參閱:https://docs.python.org/2/library/profile.html#module-cProfile


line_profiler

這個分析器能夠提供逐行水平的負載信息。這是經過 C 語言用 Cython 實現的,與 cProfile 相比計算開銷更少。

其源代碼可在 GitHub 上獲取:https://github.com/rkern/line_profiler,PyPI 頁面爲:https://pypi.python.org/pypi/line_profiler/。和 cProfile 相比,它有至關大的開銷,須要多 12 倍的時間才能獲得一個分析結果。

要使用這個工具,你首先須要經過 pip 添加:pip install pip install Cython ipython==5.4.1 line_profiler(CPython2)。這個分析器的一個主要缺點是不支持 PyPy。

就像在使用 memory_profiler 時同樣,你須要在你想分析的函數上加上一個裝飾。在咱們的例子中,你須要在 03.primes-v1.py 中的 primes 函數的定義前加上 @profile。而後像這樣調用:

你會獲得一個這樣的輸出:

咱們能夠看到兩個循環在反覆調用 list.append,佔用了腳本的大部分時間。


pprofile

地址:http://github.com/vpelletier/pprofile

據做者介紹,pprofile 是一個「行粒度的、可感知線程的肯定性和統計性純 Python 分析器」。

它的靈感來源於 line_profiler,修復了大量缺陷,但由於其徹底是用 Python 寫的,因此也能夠經過 PyPy 使用。和 cProfile 相比,使用 CPython 時分析的時間會多 28 倍,使用 PyPy 時的分析時間會長 10 倍,但具備粒度更大的細節水平。

並且還支持 PyPy 了!除此以外,它還支持線程分析,這在不少狀況下都頗有用。

要使用這個工具,你首先須要經過 pip 安裝:pip install pprofile(CPython2)/ pypy -m pip install pprofile(PyPy),而後像這樣調用:

其輸出和前面工具的輸出不一樣,以下:

咱們如今能夠看到更詳細的細節。讓咱們稍微研究一下這個輸出。這是這個腳本的整個輸出,每一行你能夠看到調用的次數、運行它所用的時間(秒)、每次調用的時間和佔全局時間的比例。此外,pprofile 還爲咱們的輸出增長了額外的行(好比 44 和 50 行,行前面寫着 (call)),這是累積指標。

一樣,咱們能夠看到有兩個循環在反覆調用 list.append,佔用了腳本的大部分時間。


vprof

地址:https://github.com/nvdv/vprof

vprof 是一個 Python 分析器,爲各類 Python 程序特色提供了豐富的交互式可視化,好比運行時間和內存使用。這是一個圖形化工具,基於 Node.JS,可在網頁上展現結果。

使用這個工具,你能夠針對相關 Python 腳本查看下面的一項或多項內容:

  • CPU flame graph
  • 代碼分析(code profiling)
  • 內存圖(memory graph)
  • 代碼熱圖(code heatmap)

要使用這個工具,你首先須要經過 pip 安裝:pip install vprof(CPython2)/ pypy -m pip install vprof(PyPy),而後像這樣調用:

在 CPython2 上,要顯示代碼熱圖(下面的第一行調用)和代碼分析(下面的第二行調用):

在 PyPy 上,要顯示代碼熱圖(下面的第一行調用)和代碼分析(下面的第二行調用):

在上面的兩個例子中,你都會看到以下的代碼熱圖:

以及以下的代碼分析:

結果是以圖形化的方式展現的,你能夠將鼠標懸浮或點擊每一行,從而查看更多信息。一樣,咱們能夠看到有兩個循環在反覆調用 list.append 方法,佔用了腳本的大部分時間。


CPU 分析——Python 解釋器

在這一節,我將介紹一些可用於在運行 Python 腳本時對解釋器進行性能分析的工具和方法。

正如前幾節提到的,CPU 性能分析的意義是同樣的,但如今咱們的目標不是 Python 腳本。咱們如今想要知道 Python 解釋器的工做方式,以及 Python 腳本運行時在哪裏消耗的時間最多。

接下來咱們將看到你能夠怎樣跟蹤 CPU 使用狀況以及找到解釋器中的熱點。


測量 CPU 使用狀況

這一節所使用的腳本基本上和前面內存分析和腳本 CPU 使用狀況分析時使用的腳本同樣,你也能夠在這裏查閱代碼:https://gist.github.com/apatrascu/44f0c6427e2df96951034b759e16946f

優化後的版本見下面或訪問:https://gist.github.com/apatrascu/ee660bf95469a55e5947a0066e930a69


CPython

CPython 的功能不少,這是徹底用 C 語言寫的,所以在測量和/或性能分析上能夠更加容易。你能夠找到託管在 GitHub 上的 CPython 資源:https://github.com/python/cpython。默認狀況下,你會看到最新的分支,在本文寫做時是 3.7+ 版本,但向前一直到 2.7 版本的分支都能找到。

在這篇文章中,咱們的重點是 CPython 2,但最新的第 3 版也可成功應用一樣的步驟。


1. 代碼覆蓋工具(Code coverage tool)

要查看正在運行的 C 語言代碼是哪一部分,最簡單的方法是使用代碼覆蓋工具。

首先咱們克隆這個代碼庫:

複製該目錄中的腳本並運行如下命令:

第一行代碼將會使用 GCOV 支持(https://gcc.gnu.org/onlinedocs/gcc/Gcov.html)編譯該解釋器,第二行將運行負載並收集在 .gcda 文件中的分析數據,第三行代碼將解析包含這些分析數據的文件並在名爲 lcov-report 的文件夾中建立一些 HTML 文件。

若是咱們在瀏覽器中打開 index.html,咱們會看到爲了運行咱們的 Python 腳本而執行的解釋器源代碼的位置。你會看到相似下面的東西:

在上面一層,咱們能夠看到構成該源代碼的每一個目錄以及被覆蓋的代碼的量。舉個例子,讓咱們從 Objects 目錄打開 listobject.c.gcov.html 文件。儘管咱們不會徹底看完這些文件,但咱們會分析其中一部分。看下面這部分。

怎麼讀懂其中的信息?在黃色一列,你能夠看到 C 語言文件代碼的行數。接下來一列是特定一行代碼執行的次數。最右邊一列是實際的 C 語言源代碼。

在這個例子中,listiter_next 方法被調用了 6000 萬次。

咱們怎麼找到這個函數?若是咱們仔細看看咱們的 Python 腳本,咱們能夠看到它使用了大量的列表迭代和 append。(這是另外一個能夠一開始就作腳本優化的地方。)

讓咱們繼續看看其它一些專用工具。在 Linux 系統上,若是咱們想要更多信息,咱們可使用 perf。官方文檔可參閱:https://perf.wiki.kernel.org/index.php/Main_Page

咱們使用下面的代碼重建了 CPython 解釋器。你應該將這個 Python 腳本下載到同一個目錄。另外,要確保你的系統安裝了 perf。

以下運行 perf。使用 perf 的更多方式能夠看 Brendan Gregg 寫的這個:http://www.brendangregg.com/perf.html

運行腳本後,你會看到下述內容:

要查看結果,運行 sudo perf report 獲取指標。

只有最相關的調用會被保留。在上面的截圖中,咱們能夠看到佔用時間最多的是 PyEval_EvalFrameEx。這是其中的主解釋器循環,在這個例子中,咱們對此並不關心。咱們感興趣的是下一個耗時的函數 listiter_next,它佔用了 10.70% 的時間。

在運行了優化的版本以後,咱們能夠看到如下結果:

在咱們優化以後,listiter_next 函數的時間佔用降至了 2.11%。讀者還能夠探索對該解釋器進行進一步的優化。


2. Valgrind/Callgrind

另外一個可用於尋找瓶頸的工具是 Valgrind,它有一個被稱爲 callgrind 的插件。更多細節請參閱:http://valgrind.org/docs/manual/cl-manual.html

咱們使用下面的代碼重建了 CPython 解釋器。你應該將這個 Python 腳本下載到同一個目錄。另外,確保你的系統安裝了 valgrind。

按下面方法運行 valgrind:

結果以下:

咱們使用 KCacheGrind 進行了可視化:http://kcachegrind.sourceforge.net/html/Home.html

PyPy

在 PyPy 上,能夠成功使用的分析器是很是有限的。PyPy 的開發者爲此開發了工具 vmprof:https://vmprof.readthedocs.io/en/latest/

首先,你要下載 PyPy:https://pypy.org/download.html。在此以後,爲其啓用 pip 支持。

安裝 vmprof 的方式很簡單,運行如下代碼便可:

按如下方式運行工做負載:

而後在瀏覽器中打開顯示在控制檯中的連接(以 http://vmprof.com/#/ 開頭的連接)。


原文連接:

https://pythonfiles.wordpress.com/2017/06/01/hunting-performance-in-python-code-part-3/
pythonfiles.wordpress.com/2017/08/24/…

相關文章
相關標籤/搜索