JIT của CPython 3.15 trở lại đúng quỹ đạo
(blog.python.org)Thành tựu chính
| Nền tảng | Mức cải thiện hiệu năng JIT (so với trình thông dịch tail-calling) |
|---|---|
| macOS AArch64 | +11~12% |
| x86_64 Linux | +5~6% |
Phạm vi benchmark: dao động từ chậm hơn 20% đến nhanh hơn trên 100% (
unpack_sequencemicrobenchmark không tính)
- Đạt mục tiêu: hoàn thành mục tiêu của 3.15 (cải thiện 5%) sớm hơn hơn 1 năm
- Hỗ trợ free-threading: vẫn chưa hoàn tất, đang được triển khai với mục tiêu cho 3.15/3.16
Bài học cốt lõi
-
Nhà tài trợ rút lui = khủng hoảng → chuyển sang cộng đồng dẫn dắt
Dù nhà tài trợ chính của nhóm Faster CPython rút lui trong năm 2025, các đóng góp tự nguyện từ cộng đồng lại giúp số lượng người đóng góp tăng lên và vẫn tạo ra kết quả. -
Hãy chia nhỏ vấn đề phức tạp
Ngay cả với một dự án có rào cản gia nhập cao như JIT, việc phân rã thành các đầu việc nhỏ + cung cấp hướng dẫn rõ ràng đã cho phép cả những người không chuyên nhưng có kinh nghiệm lập trình C cũng có thể đóng góp. -
Giảm bus factor là thước đo của một dự án lành mạnh
Số người đóng góp cho tầng trung gian (optimizer) đã tăng từ 2 lên 4, và cả các lập trình viên không thuộc nhóm core cũng đã phát triển thành những người đóng góp chủ chốt. -
Hạ tầng đo lường thay đổi tốc độ phát triển
Hệ thống báo cáo hiệu năng JIT hằng ngày (doesjitgobrrr.com) đóng vai trò quyết định trong cả việc phát hiện sớm regression lẫn tạo động lực. -
Giao lưu giữa các cộng đồng giúp nâng cao năng lực kỹ thuật
Việc trao đổi với đội PyPy và các cuộc trò chuyện không chính thức với những lập trình viên phát triển compiler đã dẫn tới tiến bộ kỹ thuật thực chất.
Bối cảnh và thành quả
[IMG] Biểu đồ hiệu năng JIT (tính đến ngày 17/3/2026, càng thấp càng nhanh hơn trình thông dịch)
(Hiệu năng JIT tính đến ngày 17/3/2026. Càng thấp càng nhanh hơn so với trình thông dịch. Nguồn ảnh: doesjitgobrrr.com)
Có một tin vui. Trên macOS AArch64, CPython JIT đã đạt mục tiêu hiệu năng (rất khiêm tốn) sớm hơn hơn 1 năm, còn trên x86_64 Linux là sớm hơn vài tháng. Bản JIT alpha của 3.15 nhanh hơn trình thông dịch tail-calling khoảng 11~12% trên macOS AArch64, và nhanh hơn trình thông dịch tiêu chuẩn 5~6% trên x86_64 Linux. Các con số này là trung bình nhân và chỉ mang tính sơ bộ. Phạm vi thực tế dao động từ chậm hơn 20% đến nhanh hơn trên 100% (unpack_sequence microbenchmark không tính). Hiện vẫn chưa có hỗ trợ free-threading hoàn chỉnh, nhưng mục tiêu là đưa vào trong 3.15/3.16. JIT hiện đã trở lại đúng quỹ đạo.
Có nhấn mạnh bao nhiêu về việc này khó đến mức nào cũng không phải là quá lời. Đã từng có lúc người ta thực sự nghi ngờ liệu dự án JIT có thể đạt được mức tăng tốc đáng kể hay không. Nhìn lại, JIT ban đầu của CPython gần như không mang lại cải thiện tốc độ nào. Tám tháng trước, tôi đã đăng một bài nhìn lại về JIT cho thấy JIT nguyên bản của CPython 3.13 và 3.14 thường còn chậm hơn cả trình thông dịch. Đó cũng là thời điểm nhóm Faster CPython mất đi sự hỗ trợ từ nhà tài trợ chính. Tôi là tình nguyện viên nên không bị ảnh hưởng trực tiếp, nhưng các đồng nghiệp làm việc ở đó thì có, và đã có lúc tương lai của JIT trông rất bất định.
Vậy điều gì đã khác so với 3.13 và 3.14? Tôi sẽ không kể một câu chuyện anh hùng rằng chúng tôi đã dùng trí tuệ để cứu JIT khỏi bờ vực thất bại. Thành thật mà nói, tôi nghĩ phần lớn thành công hiện tại đến từ may mắn. Đúng thời điểm, đúng nơi, đúng người, đúng lựa chọn. Tôi thực sự nghĩ rằng nếu thiếu dù chỉ một trong các cộng tác viên JIT cốt lõi là Savannah Ostrowski, Mark Shannon, Diego Russo, Brandt Bucher và tôi, thì điều này đã không thể xảy ra. Để không bỏ sót các cộng tác viên JIT tích cực khác, tôi xin nhắc thêm vài người: Hai Zhu, Zheaoli, Tomas Roun, Reiden Ong, Donghee Na, và có lẽ còn những người khác mà tôi chưa kể hết.
Tôi muốn nói về con người và một chút may mắn, những yếu tố thường ít được nhắc tới trong JIT. Nếu bạn tò mò về chi tiết kỹ thuật, hãy xem ở đây.
Phần 1: JIT do cộng đồng dẫn dắt
Nhóm Faster CPython đã mất nhà tài trợ chính trong năm 2025. Tôi ngay lập tức đề xuất ý tưởng quản trị do cộng đồng dẫn dắt. Khi đó, việc này có thành công hay không vẫn còn khá bất định. Dự án JIT vốn nổi tiếng là không thân thiện với người đóng góp mới. Trong lịch sử, nó đòi hỏi rất nhiều kiến thức chuyên môn từ trước.
Tại buổi core sprint của CPython ở Cambridge, nhóm core JIT đã gặp nhau và lập kế hoạch cho mục tiêu JIT nhanh hơn 5% vào 3.15, nhanh hơn 10% và có hỗ trợ free-threading vào 3.16. Có một điều ít được chú ý hơn nhưng lại thiết yếu cho sức khỏe của dự án, đó là giảm bus factor. Chúng tôi muốn có hơn 2 maintainer hoạt động tích cực ở cả ba giai đoạn của JIT: bộ chọn vùng ở frontend, optimizer ở middle-end, và bộ sinh mã ở backend.
Trước đây, middle-end của JIT chỉ có đúng 2 người đóng góp lặp lại một cách tích cực. Còn hiện nay, middle-end của JIT có 4 người đóng góp lặp lại tích cực, và 2 lập trình viên không thuộc nhóm core (Hai Zhu và Reiden) đã trưởng thành thành những thành viên giỏi và rất giá trị.
Điều hiệu quả trong việc thu hút mọi người lại là những thực hành kỹ thuật phần mềm rất phổ biến: chia nhỏ vấn đề phức tạp thành những phần có thể quản lý được. Brandt đã bắt đầu làm điều này từ 3.14, mở nhiều mega issue để chia các tối ưu hóa JIT thành những đầu việc đơn giản. Chẳng hạn như “hãy thử tối ưu một lệnh đơn trong JIT”. Tôi tiếp nối ý tưởng của Brandt và áp dụng nó cho 3.15. May mắn là tôi được nhận những việc dễ hơn, như các issue chuyển đổi lệnh của trình thông dịch sang dạng dễ tối ưu hơn. Để khuyến khích người đóng góp mới, tôi đã soạn hướng dẫn rất chi tiết theo kiểu có thể làm ngay lập tức. Tôi cũng phân định rõ ràng từng đơn vị công việc. Có vẻ điều này đã phát huy tác dụng. Có 11 người đóng góp, bao gồm cả tôi, đã làm việc trong issue đó và gần như chuyển đổi toàn bộ trình thông dịch sang dạng thân thiện hơn với optimizer của JIT. Điểm cốt lõi là chúng tôi đã biến JIT từ một khối mù mờ thành thứ mà ngay cả lập trình viên C không có kinh nghiệm JIT cũng có thể đóng góp.
Một số cách hiệu quả khác: khích lệ mọi người và ăn mừng cả những thành tựu lớn lẫn nhỏ. Mọi PR của JIT đều có một kết quả đầu ra rõ ràng, và tôi nghĩ điều đó đã giúp mọi người có cảm giác về phương hướng.
Nỗ lực tối ưu hóa từ cộng đồng đã mang lại kết quả. JIT đã tiến từ mức nhanh hơn 1% lên 3~4% trên x86_64 Linux (xem đường màu xanh bên dưới):
[IMG] Hiệu năng JIT so với trình thông dịch trong giai đoạn tối ưu hóa do cộng đồng thực hiện
(Nguồn ảnh: doesjitgobrrr.com)
Phần 2: Những lựa chọn may mắn
Ghi lại trace (Trace Recording)
Một lần nữa, tôi nghĩ phần lớn chuyện này là nhờ may mắn. Tại core sprint CPython ở Cambridge, Brandt đã thuyết phục tôi viết lại frontend của JIT theo hướng tracing. Ban đầu tôi không thích ý tưởng đó, nhưng rồi vì có chút tự ái, tôi nghĩ cứ thử viết lại “để chứng minh rằng nó không hoạt động”.
Bản prototype ban đầu chạy được chỉ sau 3 ngày, nhưng phải mất một tháng để nó thật sự JIT đúng cách trong khi vẫn vượt qua được toàn bộ test suite. Kết quả ban đầu rất tệ. Trên x86_64 Linux, nó chậm hơn khoảng 6%. Đúng lúc tôi sắp bỏ cuộc thì một tai nạn may mắn xảy ra: tôi đã hiểu sai đề xuất của Mark.
Mark đề xuất luồn bảng dispatch vào trình thông dịch, để trình thông dịch có hai bảng dispatch: một cho trình thông dịch bình thường và một cho tracing. Nhưng tôi đã hiểu sai và tạo ra một phiên bản cực đoan hơn: thay vì có bản tracing của các lệnh thông thường, tôi chỉ để lại một lệnh duy nhất phụ trách tracing, rồi cho mọi lệnh trong bảng thứ hai trỏ tới nó. Hóa ra đây lại là một lựa chọn cực kỳ tốt. Cách hai bảng ban đầu khiến kích thước trình thông dịch tăng gấp đôi, làm code phình to và kéo theo suy giảm hiệu năng tự nhiên nên chậm hơn rất nhiều. Bằng cách chỉ dùng đúng một lệnh và hai bảng, chúng tôi chỉ làm tăng kích thước trình thông dịch thêm đúng bằng 1 lệnh và có thể giữ cho trình thông dịch cơ bản rất nhanh. Tôi trìu mến gọi cơ chế này là dual dispatch.
Một con số cho thấy trace recording quan trọng đến mức nào: độ phủ mã JIT đã tăng 50%. Điều này có nghĩa là mọi tối ưu hóa trong tương lai, nói đơn giản, nếu không có nó sẽ kém hiệu quả hơn 50%.
Xin cảm ơn Brandt và Mark vì đã vô tình dẫn chúng tôi tới một lời giải tuyệt vời như vậy.
Loại bỏ đếm tham chiếu (Reference Count Elimination)
Một lựa chọn may mắn khác là thử loại bỏ đếm tham chiếu. Ban đầu đây là phần việc mà Matt Page thực hiện trong optimizer bytecode của CPython. Tôi nhận thấy rằng, bất chấp công việc trong optimizer bytecode, trong mã đã được JIT vẫn còn lại một nhánh (branch) cho mỗi lần giảm số đếm tham chiếu. Tôi nghĩ “thử loại bỏ nhánh này thì sao”, và hoàn toàn không biết nó sẽ giúp được bao nhiêu. Hóa ra chỉ một nhánh thôi cũng khá tốn kém, và nếu mỗi lệnh Python đều có hơn 1 nhánh thì chúng sẽ cộng dồn lại.
Một điều may mắn khác là việc này rất dễ song song hóa, đồng thời trở thành công cụ tuyệt vời để dạy mọi người về trình thông dịch và JIT. Đây là tối ưu hóa chính mà tôi dùng để phân việc cho mọi người trong JIT của Python 3.15. Phần lớn là một quá trình refactor thủ công, nhưng nó cho mọi người cơ hội học hỏi mà không bị choáng ngợp bởi những phần cốt lõi nhất của JIT.
Phần 3: Một đội ngũ tuyệt vời
Chúng tôi có một đội hạ tầng tuyệt vời. Thực ra là một người. Nói chính xác hơn, “đội” của chúng tôi hiện là 4 cỗ máy đang chạy trong tủ quần áo của Savannah. Dù vậy, Savannah đã làm được khối lượng công việc tương đương cả một đội hạ tầng cho JIT. Nếu không có cách báo cáo các con số hiệu năng, JIT đã không thể tiến nhanh như vậy. Kết quả chạy JIT hằng ngày là yếu tố thay đổi cuộc chơi trong vòng phản hồi. Nó giúp bắt được regression của hiệu năng JIT và cho phép chúng tôi xác nhận rằng các tối ưu hóa thực sự có tác dụng.
Mark xuất sắc về mặt kỹ thuật. Tôi sẽ không nói thêm vì tôi nghĩ Internet đã dành quá nhiều lời ca ngợi cho anh ấy rồi :).
Diego cũng rất xuất sắc. Anh ấy phụ trách JIT trên phần cứng ARM và gần đây bắt đầu làm cho JIT thân thiện hơn với profiler. Có nhấn mạnh bao nhiêu về độ khó của vấn đề này cũng không quá lời.
Brandt là người đặt nền móng ban đầu cho backend mã máy. Nếu không có điều đó, những người đóng góp mới đã phải tự viết assembler, và có lẽ điều này sẽ khiến nhiều người nản lòng hơn nữa.
Phần 4: Trò chuyện với mọi người
Tôi muốn nhấn mạnh giá trị của việc trò chuyện với mọi người và chia sẻ ý tưởng.
Xin cảm ơn CF Bolz-Tereick, người đã dạy tôi rất nhiều về PyPy. Trong vài tháng, tôi đã đọc mã nguồn của PyPy, và tôi nghĩ điều đó đã giúp tôi trở thành một lập trình viên JIT tốt hơn nói chung. CF đã rất tử tế giúp đỡ mỗi khi tôi cần.
Tôi và Max Bernstein vẫn tham gia các buổi trò chuyện compiler mang tính giao lưu, và nếu không có điều đó có lẽ tôi đã mất động lực từ lâu rồi. Max là một tác giả rất năng suất và là một chuyên gia compiler thân thiện.
Ý tưởng không tồn tại trong không gian cô lập. Tôi nghĩ việc giao lưu với các lập trình viên compiler trong một thời gian đã giúp tôi viết JIT tốt hơn. Ít nhất thì việc xem PyPy cũng đã mở rộng góc nhìn của tôi!
Kết luận
Con người là điều quan trọng. Và cộng thêm một chút may mắn nữa, JIT go brrr.
Chưa có bình luận nào.