Python3.12から、PythonスクリプトでPerfが取れるようになったそうなので試してみました。12
元記事についてコメントを頂き、ミスが分かったため改版しました。どうもありがとうございました。(2024/7/14)
- Pyenvの導入について (y-vectorfieldさん)
- Pythonのビルドフラグと環境変数の設定 (dameyodamedameさん)
事前準備
(1) PythonがLinux perfに対応しているかを確認します。sysconfig
のPY_HAVE_PERF_TRAMPOLINE
が設定されていれば良いようです。1
$ python -m sysconfig | grep HAVE_PERF_TRAMPOLINE
PY_HAVE_PERF_TRAMPOLINE = "1"
(2) CPythonのビルドオプションに-fno-omit-frame-pointer
, -mno-omit-leaf-frame-pointer
があったかを確認します。これは、perfが解析に利用するフレームポインタを保持するためのビルドオプション(gcc)のようです。
$ python -m sysconfig | grep 'no-omit-frame-pointer'
CFLAGS = "-fno-strict-overflow -DNDEBUG -g -O3 -Wall -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
(3) ユーザー権限でカーネル解析ができるようにシステムのkptr_restrict
とperf_event_paranoid
のレベルを変更しておきます。
$ echo 0 | sudo tee /proc/sys/kernel/kptr_restrict > /dev/null
$ echo 0 | sudo tee /proc/sys/kernel/perf_event_paranoid > /dev/null
筆者の環境ではPythonが上記(1), (2)に対応していませんでした。
対応手順をpyenvで行うことにしました。3
$ export PYTHON_CFLAGS='-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer'
$ pyenv install 3.12.4
$ pyenv local 3.12.4
$ python --version
Python 3.12.4
$ python -m sysconfig | grep HAVE_PERF_TRAMPOLINE
PY_HAVE_PERF_TRAMPOLINE = "1"
$ python -m sysconfig | grep 'no-omit-frame-pointer'
CFLAGS = "-fno-strict-overflow -DNDEBUG -g -O3 -Wall -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
perfの取得
サンプルコード1でコールグラフを取得してみました。
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(10000000)
元記事のコメントで頂いたとおり、perf record
ではPYTHONPERFSUPPORT=1
を環境変数で指定する必要がありました。3
この環境変数は実行時にこの機能をアクティベートするための条件になっているようです。45
$ PYTHONPERFSUPPORT=1 perf record -F 9999 -g -o perf.data python example.py
[ perf record: Woken up 4 times to write data ]
[ perf record: Captured and wrote 0.944 MB perf.data (4448 samples) ]
$ perf report --stdio -g -n
:
# Children Self Samples Command Shared Object Symbol
# ........ ........ ............ ........ .................... ..............................................................................
#
92.93% 0.00% 0 python libc.so.6 [.] __libc_start_call_main
|
---__libc_start_call_main
Py_BytesMain
|
|--91.32%--Py_RunMain
| |
| --90.96%--pymain_run_python.constprop.0
| |
| --90.94%--_PyRun_AnyFileObject
| _PyRun_SimpleFileObject
| |
| --90.91%--run_mod
| |
| --90.89%--run_eval_code_obj
| PyEval_EvalCode
| py::<module>:.../example.py
| _PyEval_EvalFrameDefault
| |
| --90.87%--PyObject_Vectorcall
| py::baz:.../example.py
| _PyEval_EvalFrameDefault
| PyObject_Vectorcall
| py::bar:.../example.py
| _PyEval_EvalFrameDefault
| PyObject_Vectorcall
| py::foo:.../example.py
| |
| |--81.36%--_PyEval_EvalFrameDefault
:
コールグラフが取得できているようでした。(関数のトレース情報を含む/tmp/perf-<PID>.map
も残っていました)
環境
CPU: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
OS: Ubuntu-22.04
Kernel: 6.5.0-41-generic
Pyenv: 2.4.7
Python: 3.12.4
-
https://docs.python.org/3/howto/perf_profiling.html#python-support-for-the-linux-perf-profiler ↩ ↩2 ↩3
-
https://realpython.com/python312-perf-profiler/#install-python-312-using-pyenv ↩ ↩2
-
https://github.com/python/cpython/blob/v3.12.4/Python/initconfig.c#L152 ↩
-
https://github.com/python/cpython/blob/v3.12.4/Python/perf_trampoline.c#L427 ↩