Python 性能分析工具py-spy與Scalene


 

Py-Spy介紹

引用官方的介紹:

Py-Spy是Python程序的抽樣分析器。 它允許您可視化查看Python程序在哪些地方花了更多時間,整個監控方式無需重新啟動程序或以任何方式修改工程代碼。 Py-Spy的開銷非常低:它是用Rust編寫的,速度與編譯的Python程序不在同一個進程中運行。 這意味着Py-Spy可以安全地用於生成生產環境中的Python應用調優分析。
github:https://github.com/benfred/py-spy

安裝

pip install py-spy

安裝后使用py-spy - h可以驗證安裝,並查看使用幫助。

py-spy從命令行工作,並獲取要從中采樣的程序的PID或要運行的python程序的命令行。py-spy具有三個子命令record, topdump

  • record生成火焰圖
  • top實時查看每個函數運行時間並統計
  • dump顯示每個python線程的當前調用堆棧

 

 

使用py-spy 生成火焰圖

​ py-spy是一個非常好用而且簡單的庫,看完他的readme 介紹文檔基本就可以入手使用spy。這個工具一是可以生成profile 火焰圖,二是可以定位到程序中最耗時間的代碼的位置。它的優點在於完全不用修改代碼,相比較其他的一些性能調查工具,py-spy這一點非常棒,當你debug 一個線上正在運行的程序的時候,只需要提供進程id,py-spy 就可以直接生成火焰圖。

py-spy record -o profile.svg --pid 12345

或者

 py-spy record -o profile.svg-python myprogram.py

"12345" 為程序運行的pid,當運行這行命令的時候,py-spy 開始抽樣的程序simlple 並且生成火焰圖,我們可以等待1分鍾左右 ctrl+c 結束,這時候會在運行這行命令的當前目錄下生成 profile.svg 火焰圖, 如下圖:

undefined

 火焰圖的分析非常簡單直觀,主要是看"平頂",看圖中最下方那個峰頂是平的,那么程序的性能問題就可以從這里入手去解決,這里不詳細介紹火焰圖看法,不明白的同學可以自行百度。 

​ 通過生成火焰圖分析程序瓶頸大概率可以找到並解決80%的程序性能問題,但是還有一種問題,如果我的火焰圖沒有平頂,但是程序依舊很慢,該如何定位問題?

沒有平頂情況下,定位程序中耗時最多函數/代碼

如下圖,通過火焰圖並沒有發現程序中的平頂
undefined

 

Top功能

這時候要用到py-spy 提供的 top 命令,Top顯示了在python程序中花費最多時間的功能的實時視圖,類似於unix top命令。

py-spy top --pid 12345

py-spy top-python myprogram.py

​ 輸入上述命令后,在控制台會顯示程序實時的運行狀態,這里可以介紹一下圖中4個參數的含義, 然后可以通過按1,2,3,4 四個按鍵,讓程序按照下圖所述排序。

  1按%Own排序(當前在該函數中花費的時間的百分比)
  2按%Total排序(函數及其子級中當前的時間百分比)
  3按OwnTime排序(函數中花費的總時間)
  4按TotalTime排序(該函數及其子項花費的總時間)

​ 比較直觀的 使用3 , 可以比較直接的看出程序運行中,所占比消耗時間最多的函數,然后從函數如圖進行分析,如下圖,可以看出 是wrap 裝飾器函數消耗的時間最長,我們用wrapt 這個c寫的裝飾器進行替換后效率有了明顯的提升。

​ undefined

總結 : 使用py-spy 相對於其他一些python性能分析工具,優勢在於使用非常簡單,而且無須對代碼做任何改動,並且可以在保護現場情況下,直接生成火焰圖,還可查看實時程序運行狀態。

 

火焰圖怎么看

首先你需要知道:

X方向是采樣時間。

Y方向是函數調用棧。

如果給你一個這樣的火焰圖,你應該得出什么信息:

1.a()是開始的執行函數,但沒有消耗cpu,在這個函數里執行了b(),h()。

2.a()的兩個分支b()h(),這表明a()里面可能有一個條件語句,繼續可以看到b()分支消耗的 CPU 大大高於h()。

3.h()函數沒有消耗cpu,cpu全被i()函數占有。

4.b()函數這條支路繼續往上,一直到d(),由d()函數的子函數e()消耗一部分cpu,f()下的g()消耗一部分cpu,你會發現d()的最右邊往上缺了一塊,這塊就是d()執行消耗的cpu。

結論:

消耗cpu的函數為e(),g(),d(),i()。

因此,如果要調查性能問題,首先應該調查g(),其次是i()

 

Scalene

Scalene 是一個 Python 的高性能 CPU內存分析器,它可以做到很多其他Python分析器不能做到的事情。它在能提供更多詳細信息的同時,比其他的分析器要快幾個數量級。

  1. Scalene 是 很快的。它使用采樣的方式而不是直接測量或者依靠Python的追蹤工具。它的開銷一般不超過10-20% (通常更少)。

  2. Scalene 是 精確的。和大部分其他的Python分析器不同,Scalene 在 行級別 下執行CPU分析,在你的程序中指出對應代碼行的執行時間。和大多數分析器所返回的功能級分析結果相比,這種程度的細節可能會更有用。

  3. Scalane 可以區分在Python中運行的時間和在native代碼(包括庫)中花費的時間。大多數的Python程序員並不會去優化native代碼(通常在Python實現中或者所依賴的外部庫),所以區分這兩種運行時間,有助於開發者能夠將優化的工作專注於他們能夠實際改善的代碼上。

  4. Scalene 可以 分析內存使用情況。除了追蹤CPU使用情況,Scalene還指出對應代碼行的內存增長。這是通過指定內存分配器來實現的。

  5. NEW! Scalene 會生成 每行 的內存分析,以此更容易的追蹤內存泄露。

  6. NEW! Scalene 會分析 內存拷貝量, 從而易於發現意外的內存拷貝。特別是因為跨越Python和底層庫的邊界導致的意外 (例如:意外的把 numpy 數組轉化成了Python數組,反之亦然)。

安裝

Scalene 通過 pip 包的形式進行分發,可以運行在Mac OS X和Linux平台(包括在Windows WSL2中運行的Ubuntu)。

你可以通過下面的方式安裝:

% pip install scalene

或者

% python -m pip install scalene

注意: 現在這樣安裝Scalene,是不會安裝內存分析的庫,所以你只能用它來執行CPU的分析。如果要使用它的內存分析能力,你需要下載這個代碼倉庫。

NEW: 你現在可以通過以下命令,在 Mac OS X 上使用 brew 安裝內存分析的部分:

% brew tap emeryberger/scalene
% brew install --head libscalene

這將會安裝一個你可以使用的 scalene 腳本(下面會提到)。

使用

下面的命令會讓 Scalene 在提供的示例程序上執行 行級別的CPU分析。

% python -m scalene test/testme.py

如果你使用Homebrew安裝 Scalene 庫,你只需要執行 scalene 就可以執行行級別的CPU和內存分析:

% scalene test/testme.py

否則,你需要運行 make 來先構建一個指定的內存分配器:

% make

在 Mac OS X 系統上進行分析(不使用Homebrew安裝):

% DYLD_INSERT_LIBRARIES=$PWD/libscalene.dylib PYTHONMALLOC=malloc python -m scalene test/testme.py

在Linux系統上分析:

% LD_PRELOAD=$PWD/libscalene.so PYTHONMALLOC=malloc python -m scalene test/testme.py

執行時增加 --help 來查看全部配置:

% python3 -m scalene --help
usage: scalene [-h] [-o OUTFILE] [--profile-interval PROFILE_INTERVAL]
[--wallclock]
prog

Scalene: a high-precision CPU and memory profiler.
https://github.com/emeryberger/Scalene

for CPU profiling only:
% python -m scalene yourprogram.py
for CPU and memory profiling (Mac OS X):
% DYLD_INSERT_LIBRARIES=$PWD/libscalene.dylib PYTHONMALLOC=malloc python -m scalene yourprogram.py
for CPU and memory profiling (Linux):
% LD_PRELOAD=$PWD/libscalene.so PYTHONMALLOC=malloc python -m scalene yourprogram.py

positional arguments:
prog program to be profiled

optional arguments:
-h, --help show this help message and exit
-o OUTFILE, --outfile OUTFILE
file to hold profiler output (default: stdout)
--profile-interval PROFILE_INTERVAL
output profiles every so many seconds.
--wallclock use wall clock time (default: virtual time)

對比其他分析器

性能和功能

下面的表格把 scalene 和不同分析器的性能做了比較。運行的示例程序 (benchmarks/julia1_nopil.py) 來自於 Gorelick 和 Ozsvald 的 《高性能Python編程》。所有的這些結果都是在 2016款 MacBook Pro上運行的。

這個表格是其他分析器 vs. Scalene 的功能比較。

輸出

Scalene 打印被分析程序中帶注釋的源代碼,以及程序在同目錄和子目錄使用到的任何模塊。下面是一個來自 pystone.py pystone.py 的片段,只使用了CPU分析:

benchmarks/pystone.py: % of CPU time = 100.00% out of   3.66s.
| CPU % | CPU % |
Line | (Python) | (native) | [benchmarks/pystone.py]
--------------------------------------------------------------------------------
[... lines omitted ...]
137 | 0.27% | 0.14% | def Proc1(PtrParIn):
138 | 1.37% | 0.11% | PtrParIn.PtrComp = NextRecord = PtrGlb.copy()
139 | 0.27% | 0.22% | PtrParIn.IntComp = 5
140 | 1.37% | 0.77% | NextRecord.IntComp = PtrParIn.IntComp
141 | 2.47% | 0.93% | NextRecord.PtrComp = PtrParIn.PtrComp
142 | 1.92% | 0.78% | NextRecord.PtrComp = Proc3(NextRecord.PtrComp)
143 | 0.27% | 0.17% | if NextRecord.Discr == Ident1:
144 | 0.82% | 0.30% | NextRecord.IntComp = 6
145 | 2.19% | 0.79% | NextRecord.EnumComp = Proc6(PtrParIn.EnumComp)
146 | 1.10% | 0.39% | NextRecord.PtrComp = PtrGlb.PtrComp
147 | 0.82% | 0.06% | NextRecord.IntComp = Proc7(NextRecord.IntComp, 10)
148 | | | else:
149 | | | PtrParIn = NextRecord.copy()
150 | 0.82% | 0.32% | NextRecord.PtrComp = None
151 | | | return PtrParIn

 

refer:

https://yq.aliyun.com/articles/741628

http://www.python88.com/topic/62116


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM