- Dự án CPython gần đây đã áp dụng một chiến lược triển khai mới cho trình thông dịch bytecode. Kết quả ban đầu cho thấy hiệu năng trung bình tăng 10-15% trên nhiều nền tảng
- Tuy nhiên, mức tăng hiệu năng này chủ yếu là kết quả của việc né tránh một vấn đề hồi quy trong LLVM 19. Khi so với các mốc chuẩn tốt hơn (ví dụ: GCC, clang-18, LLVM 19 với các cờ tinh chỉnh cụ thể), mức cải thiện giảm xuống còn 1-5%
Kết quả hiệu năng
- Đã benchmark nhiều bản build của trình thông dịch CPython bằng nhiều trình biên dịch và tùy chọn cấu hình khác nhau. Việc thử nghiệm được thực hiện trên máy chủ Intel và Apple M1 Macbook Air.
- Tất cả các bản build đều sử dụng LTO và PGO. Lấy
clang18 làm mốc chuẩn và dùng giá trị trung bình được báo cáo bởi pypeformance/pyperf compare_to.
- So sánh hiệu năng trình biên dịch
- Apple M1 Macbook Air :
- clang18: mốc chuẩn
- clang19: chậm hơn 1.12 lần
- clang19.taildup: chậm hơn 1.02 lần
- clang19.tc: chậm hơn 1.00 lần
- gcc: N/A
- Trình thông dịch tail-call vẫn cho thấy cải thiện tốc độ so với clang-18, nhưng khi chuyển sang clang-19 thì mức sụt giảm tốc độ còn rõ rệt hơn.
Hồi quy của LLVM
Bối cảnh ngắn gọn
- Trình thông dịch bytecode truyền thống được cấu thành từ câu lệnh
switch bên trong vòng lặp while. Hầu hết trình biên dịch sẽ biên dịch switch thành bảng nhảy.
- Các trình biên dịch C hiện đại hỗ trợ mẫu lấy địa chỉ của nhãn và dùng nó như một "computed goto". CPython đã dùng mẫu này cho tới trước khi có công việc về tail-call.
Hồi quy trong LLVM 19
- LLVM 19 đã đặt giới hạn cho pass tail-duplication, khiến việc sao chép dừng lại nếu kích thước IR vượt quá một ngưỡng nhất định. Vì thế trong CPython, mọi cú nhảy dispatch đều bị gộp lại, hoàn toàn làm mất mục đích của cách triển khai dựa trên
goto tính toán.
Các hiện tượng bất thường khác
- Có thể chắc chắn rằng thay đổi trong logic nhân bản tail-call đã gây ra hồi quy, nhưng vẫn chưa thể giải thích đầy đủ mức độ lớn của hồi quy này.
- Trên các bộ xử lý hiện đại, mức cải thiện tốc độ 2-4% là phổ biến hơn.
Có cần computed goto không?
- Benchmark
clang19.nocg được cho là nhanh hơn clang19. Điều này cho thấy trình biên dịch có thể thực hiện cùng kiểu tối ưu hóa đó với trình thông dịch dựa trên switch.
Bản sửa lỗi
- Pull request LLVM 114990 đã sửa lỗi hồi quy này. Bản sửa khôi phục hiệu năng như kỳ vọng.
Suy ngẫm
Về benchmark
- Khi tối ưu hóa hệ thống, người ta xây dựng benchmark và phương pháp benchmark để đánh giá các thay đổi được đề xuất.
- Benchmark đòi hỏi nhiều giả định và niềm tin hơn để có thể khái quát hóa từ các điểm dữ liệu cụ thể.
Mốc chuẩn
- Khi đề xuất một giải pháp hay phương pháp mới, thông lệ là so sánh với "cách tiếp cận tốt nhất đang được biết đến hiện tại".
Về kỹ thuật phần mềm
- Các hệ thống phần mềm rất phức tạp, liên kết chặt chẽ với nhau và thay đổi nhanh chóng.
- Trình biên dịch tối ưu hóa luôn ở trong thế căng giữa việc phải tôn trọng ý đồ của lập trình viên và việc tối ưu mã.
Trình biên dịch tối ưu hóa
- Thuộc tính
musttail đại diện cho một loại tính năng trình biên dịch mới liên quan đến tối ưu hóa. Nó có thể mang lại một phong cách mạnh hơn để viết mã nhạy cảm với hiệu năng.
Thêm một điều về nix
nix đã rất hữu ích trong dự án này. Nó hỗ trợ rất nhiều cho việc quản lý và build nhiều phiên bản trình thông dịch Python.
1 bình luận
Ý kiến trên Hacker News
Xin chào. Tôi là tác giả của PR đưa trình thông dịch tail-calling vào CPython
Benchmark thực sự là một công việc cực kỳ khó làm cho đúng
Xin dành lời khen cho tác giả vì đã lần ra sự thật của vấn đề này
Đây là một ví dụ hay cho thấy C không phải là ngôn ngữ "gần với máy"
switch()theo kiểu "ngây thơ"Do cách trình biên dịch điều chỉnh việc tổ chức vòng lặp, trình thông dịch tail-call không hiệu quả như mức đã được công bố
Việc đánh giá hiệu năng của một bản build Python là rất khó
Thảo luận liên quan:
Bài viết rất hay
Gần đây tôi đã benchmark từ Python 3.9 đến 3.13
Tôi thắc mắc tối ưu hóa này liên quan thế nào đến tail-call optimization