Hỗ trợ Python cho trình lược tả Linux perf¶
- tác giả:
Pablo Galindo
The Linux perf profiler là một công cụ rất mạnh mẽ cho phép bạn lập hồ sơ và thu thập thông tin về hiệu suất ứng dụng của mình. perf cũng có một hệ sinh thái công cụ rất sôi động hỗ trợ việc phân tích dữ liệu mà nó tạo ra.
Vấn đề chính khi sử dụng trình lược tả perf với các ứng dụng Python là perf chỉ lấy thông tin về các ký hiệu gốc, tức là tên của các hàm và thủ tục được viết bằng C. Điều này có nghĩa là tên và tên tệp của các hàm Python trong mã của bạn sẽ không xuất hiện trong đầu ra của perf.
Kể từ Python 3.12, trình thông dịch có thể chạy ở chế độ đặc biệt cho phép các hàm Python xuất hiện trong đầu ra của trình lược tả perf. Khi chế độ này được bật, trình thông dịch sẽ chèn một đoạn mã nhỏ được biên dịch nhanh chóng trước khi thực thi mọi hàm Python và nó sẽ dạy cho perf mối quan hệ giữa đoạn mã này và hàm Python liên quan bằng perf map files.
Ghi chú
Hỗ trợ cho trình lược tả perf hiện chỉ khả dụng cho Linux trên một số kiến trúc được chọn. Kiểm tra đầu ra của bước xây dựng configure hoặc kiểm tra đầu ra của python -m sysconfig | grep HAVE_PERF_TRAMPOLINE để xem hệ thống của bạn có được hỗ trợ hay không.
Ví dụ: hãy xem xét tập lệnh sau:
def foo(n):
kết quả = 0
cho _ trong phạm vi (n):
kết quả += 1
kết quả trả về
thanh def (n):
foo(n)
def baz(n):
thanh(n)
nếu __name__ == "__main__":
baz(1000000)
Chúng tôi có thể chạy perf để lấy mẫu dấu vết ngăn xếp CPU ở 9999 hertz:
$ bản ghi hoàn hảo -F 9999 -g -o perf.data python my_script.py
Sau đó chúng ta có thể sử dụng perf report để phân tích dữ liệu:
Báo cáo hoàn hảo $ --stdio -n -g
# Children Lệnh Tự Mẫu Biểu tượng Đối tượng Chia sẻ
# ......... ............. ............ ....................................................
#
91.08% 0,00% 0 python.exe python.exe [.] _start
|
---_bắt đầu
|
--90,71%--__libc_start_main
Py_BytesChính
|
|--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
...
Như bạn có thể thấy, các hàm Python không được hiển thị trong đầu ra, chỉ có _PyEval_EvalFrameDefault (hàm đánh giá mã byte Python) hiển thị. Thật không may, điều đó không hữu ích lắm vì tất cả các hàm Python đều sử dụng cùng một hàm C để đánh giá mã byte nên chúng ta không thể biết hàm Python nào tương ứng với hàm đánh giá mã byte nào.
Thay vào đó, nếu chúng tôi chạy thử nghiệm tương tự với hỗ trợ perf được bật, chúng tôi sẽ nhận được:
Báo cáo hoàn hảo $ --stdio -n -g
# Children Lệnh tự mẫu Biểu tượng đối tượng chia sẻ
# ......... ............. ............ ....................................................................................
#
90.58% 0,36% 1 python.exe python.exe [.] _start
|
---_bắt đầu
|
--89,86%--__libc_start_main
Py_BytesChính
|
|--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
Cách bật hỗ trợ hồ sơ perf¶
Hỗ trợ định hình perf có thể được bật ngay từ đầu bằng cách sử dụng biến môi trường PYTHONPERFSUPPORT hoặc tùy chọn -X perf hoặc sử dụng động sys.activate_stack_trampoline() và sys.deactivate_stack_trampoline().
Các hàm sys được ưu tiên hơn tùy chọn -X, tùy chọn -X được ưu tiên hơn biến môi trường.
Ví dụ, sử dụng biến môi trường
$ PYTHONPERFSUPPORT=1 bản ghi hoàn hảo -F 9999 -g -o perf.data python my_script.py
$ báo cáo hoàn hảo -g -i perf.data
Ví dụ: sử dụng tùy chọn -X:
$ bản ghi hoàn hảo -F 9999 -g -o perf.data python -X hoàn hảo my_script.py
$ báo cáo hoàn hảo -g -i perf.data
Ví dụ: sử dụng API sys trong tệp example.py:
hệ thống nhập khẩu
sys.activate_stack_trampoline("perf")
do_profiled_stuff()
sys.deactivate_stack_trampoline()
non_profiled_stuff()
...sau đó:
$ bản ghi hoàn hảo -F 9999 -g -o perf.data python ./example.py
$ báo cáo hoàn hảo -g -i perf.data
Làm thế nào để có được kết quả tốt nhất¶
Để có kết quả tốt nhất, Python nên được biên dịch bằng CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" vì điều này cho phép trình biên dịch thư giãn chỉ bằng cách sử dụng con trỏ khung chứ không phải trên thông tin gỡ lỗi DWARF. Điều này là do mã được xen vào để cho phép hỗ trợ perf được tạo động nên nó không có sẵn bất kỳ thông tin gỡ lỗi DWARF nào.
Bạn có thể kiểm tra xem hệ thống của bạn đã được biên dịch bằng cờ này chưa bằng cách chạy:
$ python -m sysconfig | grep 'con trỏ không bỏ qua khung'
Nếu bạn không thấy bất kỳ đầu ra nào, điều đó có nghĩa là trình thông dịch của bạn chưa được biên dịch bằng con trỏ khung và do đó nó có thể không hiển thị được các hàm Python trong đầu ra của perf.
Cách làm việc mà không cần con trỏ khung¶
Nếu bạn đang làm việc với trình thông dịch Python đã được biên dịch mà không có con trỏ khung, bạn vẫn có thể sử dụng trình lược tả perf, nhưng chi phí sẽ cao hơn một chút vì Python cần tạo thông tin tháo gỡ cho mọi lệnh gọi hàm Python một cách nhanh chóng. Ngoài ra, perf sẽ mất nhiều thời gian hơn để xử lý dữ liệu vì nó sẽ cần sử dụng thông tin gỡ lỗi DWARF để giải phóng ngăn xếp và đây là một quá trình chậm.
Để bật chế độ này, bạn có thể sử dụng biến môi trường PYTHON_PERF_JIT_SUPPORT hoặc tùy chọn -X perf_jit, tùy chọn này sẽ bật chế độ JIT cho trình lược tả perf.
Ghi chú
Do công cụ perf bị lỗi nên chỉ những phiên bản perf cao hơn v6.8 mới hoạt động với chế độ JIT. Bản sửa lỗi cũng đã được chuyển sang phiên bản v6.7.2 của công cụ.
Lưu ý rằng khi kiểm tra phiên bản của công cụ perf (có thể thực hiện bằng cách chạy perf version), bạn phải tính đến việc một số bản phân phối thêm một số số phiên bản tùy chỉnh bao gồm ký tự -. Điều này có nghĩa là perf 6.7-3 không nhất thiết phải là perf 6.7.3.
Khi sử dụng chế độ JIT hoàn hảo, bạn cần thực hiện thêm một bước trước khi có thể chạy perf report. Bạn cần gọi lệnh perf inject để đưa thông tin JIT vào tệp perf.data.:
$ bản ghi hoàn hảo -F 9999 -g -k 1 --call-graph lùn -o perf.data python -Xperf_jit my_script.py
$ perf tiêm -i perf.data --jit --output perf.jit.data
$ báo cáo hoàn hảo -g -i perf.jit.data
hoặc sử dụng biến môi trường
$ PYTHON_PERF_JIT_SUPPORT=1 bản ghi hoàn hảo -F 9999 -g --call-graph lùn -o perf.data python my_script.py
$ perf tiêm -i perf.data --jit --output perf.jit.data
$ báo cáo hoàn hảo -g -i perf.jit.data
Lệnh perf inject --jit sẽ đọc perf.data, tự động chọn tệp kết xuất hoàn hảo mà Python tạo (trong /tmp/perf-$PID.dump), sau đó tạo perf.jit.data để hợp nhất tất cả thông tin JIT lại với nhau. Nó cũng sẽ tạo ra nhiều tệp jitted-XXXX-N.so trong thư mục hiện tại là hình ảnh ELF cho tất cả các tấm bạt lò xo JIT được tạo bởi Python.
Cảnh báo
Khi sử dụng --call-graph dwarf, công cụ perf sẽ chụp ảnh nhanh ngăn xếp của quá trình đang được lược tả và lưu thông tin vào tệp perf.data. Theo mặc định, kích thước của kết xuất ngăn xếp là 8192 byte, nhưng bạn có thể thay đổi kích thước bằng cách chuyển nó sau dấu phẩy như --call-graph dwarf,16384.
Kích thước của kết xuất ngăn xếp rất quan trọng vì nếu kích thước quá nhỏ perf sẽ không thể giải phóng ngăn xếp và đầu ra sẽ không đầy đủ. Mặt khác, nếu kích thước quá lớn thì perf sẽ không thể lấy mẫu quy trình thường xuyên như mong muốn vì chi phí sẽ cao hơn.
Kích thước ngăn xếp đặc biệt quan trọng khi định hình mã Python được biên dịch ở mức tối ưu hóa thấp (như -O0), vì các bản dựng này có xu hướng có khung ngăn xếp lớn hơn. Nếu bạn đang biên dịch Python bằng -O0 và không thấy các hàm Python trong đầu ra lược tả của mình, hãy thử tăng kích thước kết xuất ngăn xếp lên 65528 byte (tối đa):
$ bản ghi hoàn hảo -F 9999 -g -k 1 --call-graph lùn,65528 -o perf.data python -Xperf_jit my_script.py
Các cờ biên dịch khác nhau có thể tác động đáng kể đến kích thước ngăn xếp:
Các bản dựng có
-O0thường có khung ngăn xếp lớn hơn nhiều so với các bản dựng có-O1trở lênViệc thêm các tối ưu hóa (
-O1,-O2, v.v.) thường làm giảm kích thước ngăn xếpCon trỏ khung (
-fno-omit-frame-pointer) thường cung cấp khả năng giải phóng ngăn xếp đáng tin cậy hơn