Linux perf
分析器的 Python 支援¶
- 作者:
Pablo Galindo
Linux perf 分析器是一個非常強大的工具,可讓您分析並獲取有關應用程式效能的資訊。perf
還擁有一個非常活躍的工具生態系統,可幫助分析其生成的資料。
在 Python 應用程式中使用 perf
分析器的主要問題是 perf
僅獲取有關本機符號的資訊,即用 C 編寫的函式和過程的名稱。這意味著 Python 程式碼中 Python 函式的名稱和檔名將不會出現在 perf
的輸出中。
從 Python 3.12 開始,直譯器可以在特殊模式下執行,該模式允許 Python 函數出現在 perf
分析器的輸出中。 啟用此模式後,直譯器將在每次執行 Python 函式之前插入一段即時編譯的程式碼,並使用 perf 對映檔案 將此程式碼片段與關聯的 Python 函式之間的關係告知 perf
。
注意
目前,perf
分析器的支援僅在 Linux 上適用於特定的體系結構。 請檢查 configure
構建步驟的輸出,或檢查 python -m sysconfig | grep HAVE_PERF_TRAMPOLINE
的輸出,以檢視您的系統是否受支援。
例如,考慮以下指令碼
def foo(n):
result = 0
for _ in range(n):
result += 1
return result
def bar(n):
foo(n)
def baz(n):
bar(n)
if __name__ == "__main__":
baz(1000000)
我們可以執行 perf
以 9999 赫茲的頻率取樣 CPU 堆疊跟蹤
$ perf record -F 9999 -g -o perf.data python my_script.py
然後,我們可以使用 perf report
分析資料
$ perf report --stdio -n -g
# Children Self Samples Command Shared Object Symbol
# ........ ........ ............ .......... .................. ..........................................
#
91.08% 0.00% 0 python.exe python.exe [.] _start
|
---_start
|
--90.71%--__libc_start_main
Py_BytesMain
|
|--56.88%--pymain_run_python.constprop.0
| |
| |--56.13%--_PyRun_AnyFileObject
| | _PyRun_SimpleFileObject
| | |
| | |--55.02%--run_mod
| | | |
| | | --54.65%--PyEval_EvalCode
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | |
| | | |--51.67%--_PyEval_EvalFrameDefault
| | | | |
| | | | |--11.52%--_PyLong_Add
| | | | | |
| | | | | |--2.97%--_PyObject_Malloc
...
如您所見,輸出中未顯示 Python 函式,僅顯示 _PyEval_EvalFrameDefault
(評估 Python 位元組碼的函式)。 遺憾的是,這並不是很有用,因為所有 Python 函式都使用同一個 C 函式來評估位元組碼,因此我們無法知道哪個 Python 函式對應於哪個位元組碼評估函式。
相反,如果我們啟用 perf
支援執行相同的實驗,我們會得到
$ perf report --stdio -n -g
# Children Self Samples Command Shared Object Symbol
# ........ ........ ............ .......... .................. .....................................................................
#
90.58% 0.36% 1 python.exe python.exe [.] _start
|
---_start
|
--89.86%--__libc_start_main
Py_BytesMain
|
|--55.43%--pymain_run_python.constprop.0
| |
| |--54.71%--_PyRun_AnyFileObject
| | _PyRun_SimpleFileObject
| | |
| | |--53.62%--run_mod
| | | |
| | | --53.26%--PyEval_EvalCode
| | | py::<module>:/src/script.py
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | py::baz:/src/script.py
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | py::bar:/src/script.py
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | py::foo:/src/script.py
| | | |
| | | |--51.81%--_PyEval_EvalFrameDefault
| | | | |
| | | | |--13.77%--_PyLong_Add
| | | | | |
| | | | | |--3.26%--_PyObject_Malloc
如何啟用 perf
分析支援¶
可以使用環境變數 PYTHONPERFSUPPORT
或 -X perf
選項從一開始就啟用 perf
分析支援,也可以使用 sys.activate_stack_trampoline()
和 sys.deactivate_stack_trampoline()
動態啟用。
sys
函式優先於 -X
選項,-X
選項優先於環境變數。
示例,使用環境變數
$ PYTHONPERFSUPPORT=1 perf record -F 9999 -g -o perf.data python script.py
$ perf report -g -i perf.data
示例,使用 -X
選項
$ perf record -F 9999 -g -o perf.data python -X perf script.py
$ perf report -g -i perf.data
示例,使用檔案 example.py
中的 sys
API
import sys
sys.activate_stack_trampoline("perf")
do_profiled_stuff()
sys.deactivate_stack_trampoline()
non_profiled_stuff()
...然後
$ perf record -F 9999 -g -o perf.data python ./example.py
$ perf report -g -i perf.data
如何獲得最佳結果¶
為了獲得最佳結果,應該使用 CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
編譯 Python,因為這允許分析器僅使用幀指標進行展開,而不是使用 DWARF 除錯資訊。 這是因為用於允許 perf
支援的程式碼是動態生成的,因此沒有任何可用的 DWARF 除錯資訊。
您可以透過執行以下命令來檢查系統是否已使用此標誌進行編譯
$ python -m sysconfig | grep 'no-omit-frame-pointer'
如果看不到任何輸出,則表示您的直譯器沒有使用幀指標進行編譯,因此它可能無法在 perf
的輸出中顯示 Python 函式。
如何在沒有幀指標的情況下工作¶
如果您使用的是未編譯幀指標的 Python 直譯器,您仍然可以使用 perf
分析器,但開銷會稍高,因為 Python 需要為每個 Python 函式呼叫動態生成展開資訊。 此外,perf
將花費更多時間來處理資料,因為它需要使用 DWARF 除錯資訊來展開堆疊,這是一個緩慢的過程。
要啟用此模式,您可以使用環境變數 PYTHON_PERF_JIT_SUPPORT
或 -X perf_jit
選項,這將為 perf
分析器啟用 JIT 模式。
注意
由於 perf
工具中的一個錯誤,只有高於 v6.8 的 perf
版本才能在 JIT 模式下工作。 該修復程式也已向後移植到 v6.7.2 版本的工具中。
請注意,在檢查 perf
工具的版本時(可以透過執行 perf version
來完成),必須考慮到某些發行版會新增一些自定義版本號,其中包括 -
字元。 這意味著 perf 6.7-3
不一定是 perf 6.7.3
。
當使用 perf JIT 模式時,您需要在執行 perf report
之前執行一個額外的步驟。 您需要呼叫 perf inject
命令將 JIT 資訊注入到 perf.data
檔案中。
$ perf record -F 9999 -g --call-graph dwarf -o perf.data python -Xperf_jit my_script.py
$ perf inject -i perf.data --jit --output perf.jit.data
$ perf report -g -i perf.jit.data
或者使用環境變數
$ PYTHON_PERF_JIT_SUPPORT=1 perf record -F 9999 -g --call-graph dwarf -o perf.data python my_script.py
$ perf inject -i perf.data --jit --output perf.jit.data
$ perf report -g -i perf.jit.data
perf inject --jit
命令將讀取 perf.data
,自動選取 Python 建立的 perf 轉儲檔案(位於 /tmp/perf-$PID.dump
中),然後建立 perf.jit.data
,其中合併了所有 JIT 資訊。 它還應該在當前目錄中建立許多 jitted-XXXX-N.so
檔案,這些檔案是 Python 建立的所有 JIT trampoline 的 ELF 映像。
警告
請注意,當使用 --call-graph dwarf
時,perf
工具將拍攝正在分析的程序的堆疊快照,並將資訊儲存在 perf.data
檔案中。 預設情況下,堆疊轉儲的大小為 8192 位元組,但使用者可以透過在逗號後傳遞大小來更改大小,例如 --call-graph dwarf,4096
。 堆疊轉儲的大小很重要,因為如果大小太小,perf
將無法展開堆疊,並且輸出將不完整。 另一方面,如果大小太大,則 perf
將無法像它希望的那樣頻繁地取樣程序,因為開銷會更高。