Frame pointer (and any per-function features) builds of tail calling interpreter slower than expected
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 77.2k
- Fork
- 35.9k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
Bug report
Bug description:
When building with the normal interpreter (computed goto), the impact of frame pointers should only be around 2% geomean max.
However, it seems the impact on the tail calling interpreter is higher. This is likely because each bytecode handler on the tail calling interpreter is now a function, which means they each have a frame pointer prologue and epilogue.
There is a fix on Clang 21 and higher (though I don't know if GCC 16 has it): omit the frame pointer, but reserve it so it doesn't get clobbered only for the bytecode handlers, leave the rest building with frame pointers. The flags are "-fomit-frame-pointer -momit-leaf-frame-pointer -mreserve-frame-pointer-reg". The key observation is that since we tail call, we only need the first entry into a tail calling handler to have the frame pointer prologue (ie LABEL(start_frame) needs the prologue), but all other tail calling function pointers do not. This practically eliminates the entire prologue/epilogue overhead for the interpreter, while keeping register allocation good and not clobbering frame pointers. Unfortunately, clang does seem not have custom per-function attributes for this option, so we have to move the non-starter bytecode handlers to their own compilation unit (ie, move them to their own C file).
This seems to affect anything with per-function overhead as well, such as CET/BTI changes. We can fix those incrementally, as the approacha re the same.
Here are some geometric mean results on Sam's fastmark (pyperformance subset) from my laptop (i7-12700H x86-64) with clang 22:
- Baseline (FP on, tail calling interpreter): 0% slowdown
- FP off, tail calling interpreter: 2.7% speedup
- FP on, tail calling interpreter, reserve register patch (meowl + garbage + oiia): 2.5% speedup
TLDR: WIth this patch, the tail calling interpreter's overhead for frame pointer enabling drops from 2.7% to just 0.2%!!!! Frame pointers are practically free on the tail calling interpreter!!! Meanwhile, computed goto interpreter has a 1.5% hit (the i7-12700h machine is mine from PEP 831)
@pablogsal @markshannon @stratakis
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu bằng cách kiểm tra trình thông dịch tail-calling quanh điểm vào LABEL(start_frame) và cấu hình build cho các handler bytecode của nó. Chạy benchmark fastmark được liên kết bằng Clang và so sánh overhead frame-pointer được báo cáo; hoàn thành có nghĩa là trình thông dịch tail-calling vẫn giữ các frame pointer mà không chịu sự chậm lại do prologue/epilogue của handler được mô tả ở đây.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- c, python
- Lĩnh vực
- build-system, compilers, performance
- Loại issue
- Lỗi
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức độ hoạt động
- Ít trao đổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 38/100