perf map 호환 프로파일러에 대한 파이썬 지원¶
Linux perf 프로파일러 와 samply 는 애플리케이션의 성능을 프로파일링하고 정보를 얻을 수 있게 해주는 강력한 도구입니다. 두 도구 모두 생성된 데이터를 분석하는 데 도움이 되는 활발한 생태계를 갖추고 있습니다.
이 프로파일러들을 파이썬 애플리케이션에서 사용할 때의 주요 문제는 네이티브 심볼, 즉 C로 작성된 함수 및 프로시저의 이름만 정보를 가져온다는 점입니다. 이는 코드 내에 있는 파이썬 함수의 이름과 파일 이름이 프로파일러 출력에 나타나지 않음을 의미합니다.
파이썬 3.12부터 인터프리터는 호환되는 프로파일러의 출력에 파이썬 함수가 표시되도록 하는 특별한 모드에서 실행될 수 있습니다. 이 모드가 활성화되면, 인터프리터는 모든 파이썬 함수 실행 전에 즉석에서 컴파일된 작은 코드 조각을 삽입하고, perf map 파일 을 사용하여 이 코드 조각과 관련 파이썬 함수 사이의 관계를 프로파일러에 알립니다.
참고
프로파일링 지원은 Linux 및 macOS의 특정 아키텍처에서 사용할 수 있습니다. Perf는 Linux에서 사용할 수 있으며, samply는 Linux와 macOS 모두에서 사용할 수 있습니다. macOS에서 samply 지원은 파이썬 3.15부터 가능합니다. 시스템이 지원되는지 확인하려면 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)
9999Hz로 CPU 스택 추적을 샘플링하기 위해 perf 를 실행할 수 있습니다:
$ 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%--_PyCompactLong_Add
| | | | | |
| | | | | |--2.97%--_PyObject_Malloc
...
보시다시피 파이썬 함수는 출력에 표시되지 않고, 오직 _PyEval_EvalFrameDefault (파이썬 바이트코드를 평가하는 함수)만 나타납니다. 안타깝게도 모든 파이썬 함수가 바이트코드를 평가하기 위해 동일한 C 함수를 사용하므로 어떤 파이썬 함수가 어떤 바이트코드 평가 함수에 해당하는지 알 수 없기 때문에 유용하지 않습니다.
대신, 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%--_PyCompactLong_Add
| | | | | |
| | | | | |--3.26%--_PyObject_Malloc
samply 프로파일러 사용하기¶
samply는 perf를 대체하여 사용할 수 있는 현대적인 프로파일러입니다. 파이썬이 생성하는 것과 동일한 perf map 파일을 사용하므로 파이썬의 프로파일링 지원과 호환됩니다. samply는 특히 perf를 사용할 수 없는 macOS에서 유용합니다.
파이썬과 함께 samply를 사용하려면 먼저 https://github.com/mstange/samply의 지침에 따라 설치한 다음 실행하십시오:
$ samply record PYTHONPERFSUPPORT=1 python my_script.py
이 명령은 프로파일링 데이터를 대화형으로 분석할 수 있는 웹 인터페이스를 엽니다. samply의 장점은 프로파일링 데이터 분석을 위한 현대적인 웹 기반 인터페이스를 제공하며 Linux와 macOS 모두에서 작동한다는 것입니다.
macOS에서 samply를 지원하려면 파이썬 3.15 이상이 필요합니다. 또한 macOS에서는 시스템 제한으로 인해 서명된 파이썬 실행 파일을 프로파일링할 수 없습니다. 직접 컴파일한 파이썬 바이너리나 서명이 없는 경우, 또는 로컬로 서명된 경우(Homebrew를 통해 설치된 모든 항목 등)에는 프로파일링이 가능합니다. macOS에서 실행 중인 프로세스에 연결하려면 samply 바이너리를 자체 서명하기 위해 samp1 setup 을 한 번(그리고 samply가 업데이트될 때마다) 실행하십시오.
perf 프로파일링 지원 활성화 방법¶
perf 프로파일링 지원은 환경 변수 PYTHONPERFSUPPORT 또는 -X perf 옵션을 사용하여 처음부터 활성화하거나, sys.activate_stack_trampoline() 및 sys.deactivate_stack_trampoline() 을 사용하여 동적으로 활성화할 수 있습니다.
sys 함수가 -X 옵션보다 우선하며, -X 옵션이 환경 변수보다 우선합니다.
예시: 환경 변수 사용:
$ PYTHONPERFSUPPORT=1 perf record -F 9999 -g -o perf.data python my_script.py
$ perf report -g -i perf.data
예시: -X 옵션 사용:
$ perf record -F 9999 -g -o perf.data python -X perf my_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
최상의 결과 얻는 방법¶
최상의 결과를 얻으려면 프레임 포인터를 활성화 상태로 유지하십시오. 지원되는 GCC 호환 툴체인에서 CPython은 -fno-omit-frame-pointer 및 유사한 플래그를 사용하여 빌드됩니다(자세한 내용은 --without-frame-pointers 참조). 이러한 플래그는 프로파일러가 DWARF 디버그 정보 대신 프레임 포인터만 사용하여 스택을 풀(unwind)할 수 있게 합니다. 이는 perf 지원을 위해 삽입되는 코드가 동적으로 생성되어 DWARF 디버깅 정보를 사용할 수 없기 때문입니다.
시스템이 이 플래그와 함께 컴파일되었는지 확인하려면 다음을 실행하십시오:
$ python -m sysconfig | grep 'no-omit-frame-pointer'
출력이 없으면 인터프리터가 프레임 포인터와 함께 컴파일되지 않았음을 의미하며, 따라서 perf 출력에 파이썬 함수가 표시되지 않을 수 있습니다.
프레임 포인터 없이 작업하는 방법¶
프레임 포인터 없이 컴파일된 파이썬 인터프리터를 사용하는 경우에도 perf 프로파일러를 사용할 수 있지만, 파이썬이 모든 함수 호출에 대해 즉석에서 풀기(unwinding) 정보를 생성해야 하므로 오버헤드가 약간 더 높습니다. 또한, perf 가 스택을 풀기 위해 DWARF 디버그 정보를 사용해야 하므로 데이터 처리 시간이 더 오래 걸립니다.
이 모드를 활성화하려면 환경 변수 PYTHON_PERF_JIT_SUPPORT 또는 perf 프로파일러의 JIT 모드를 활성화하는 -X perf_jit 옵션을 사용할 수 있습니다.
참고
perf 도구의 버그로 인해 버전 v6.8 이상에서만 JIT 모드가 작동합니다. 이 수정사항은 도구의 v6.7.2 버전에도 백포트되었습니다.
perf 도구의 버전을 확인할 때(perf version 실행), 일부 배포판에서 - 문자를 포함한 사용자 정의 버전 번호를 추가한다는 점을 유의하십시오. 이는 perf 6.7-3 이 반드시 perf 6.7.3 인 것은 아님을 의미합니다.
perf JIT 모드를 사용할 때는 perf report``를 실행하기 전에 추가 단계가 필요합니다. ``perf inject 명령을 호출하여 perf.data 파일에 JIT 정보를 주입해야 합니다.:
$ perf record -F 9999 -g -k 1 --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``를 읽고, 파이썬이 생성한 perf 덤프 파일(/tmp/perf-$PID.dump``)을 자동으로 감지한 후 모든 JIT 정보를 통합하여 perf.jit.data``를 생성합니다. 또한 현재 디렉토리에 파이썬에 의해 생성된 모든 JIT 트램폴린의 ELF 이미지인 많은 ``jitted-XXXX-N.so 파일을 생성해야 합니다.
경고
--call-graph dwarf 를 사용하면 perf 도구가 프로파일링되는 프로세스의 스택을 스냅샷으로 찍어 perf.data 파일에 저장합니다. 기본 스택 덤프 크기는 8192바이트이지만, --call-graph dwarf,16384 와 같이 쉼표 뒤에 값을 전달하여 크기를 변경할 수 있습니다.
스택 덤프 크기는 매우 중요합니다. 크기가 너무 작으면 perf 가 스택을 풀 수 없어 결과가 불완전해지기 때문입니다. 반대로 크기가 너무 크면 오버헤드가 높아져 perf 가 원하는 빈도로 프로세스를 샘플링할 수 없게 됩니다.
스택 크기는 특히 낮은 최적화 수준(예: -O0)으로 컴파일된 파이썬 코드를 프로파일링할 때 중요합니다. 이러한 빌드는 스택 프레임이 더 큰 경향이 있기 때문입니다. -O0 로 파이썬을 컴파일하고 프로파일링 결과에서 파이썬 함수가 보이지 않는다면, 스택 덤프 크기를 최대치인 65528바이트로 늘려보십시오:
$ perf record -F 9999 -g -k 1 --call-graph dwarf,65528 -o perf.data python -Xperf_jit my_script.py
컴파일 플래그에 따라 스택 크기가 크게 달라질 수 있습니다:
-O0``로 빌드한 경우 ``-O1이상으로 빌드한 경우보다 일반적으로 훨씬 큰 스택 프레임을 가집니다.최적화(
-O1,-O2등)를 추가하면 일반적으로 스택 크기가 감소합니다.프레임 포인터(
-fno-omit-frame-pointer)는 일반적으로 더 신뢰할 수 있는 스택 풀기를 제공합니다.