Backend·Hacker News·2 lượt xem

JIT của Python 3.15 hiện đã hoạt động trở lại

Python 3.15's JIT is now back on track

AI Summary

Dự án CPython JIT đã vượt tiến độ mục tiêu về hiệu năng cho Python 3.15, mang lại tốc độ cải thiện đáng kể trên macOS AArch64 (tăng 11-12%) và Linux x86_64 (tăng 5-6%). Đây là một bước tiến lớn so với các phiên bản trước, nơi JIT đôi khi còn gây chậm. Tuy nhiên, các nhà phát triển cần lưu ý rằng dù đã có nhiều tiến bộ, hỗ trợ free-threading vẫn là mục tiêu trong tương lai. Thành công này đến từ sự kết hợp giữa các đột phá kỹ thuật và sự tập trung mới vào việc quản lý cộng đồng, chia nhỏ các task phức tạp thành những vấn đề nhỏ hơn, dễ quản lý hơn cho người đóng góp.

Tin vui—chúng tôi đã đạt được mục tiêu hiệu suất (rất khiêm tốn) của mình...

Hiệu suất JIT tính đến ngày 17 tháng 3 (PST). Thấp hơn là tốt hơn so với thông dịch viên (Hiệu suất JIT tính đến ngày 17 tháng 3 (PST). Thấp hơn sẽ tốt hơn so với thông dịch viên. Ghi công hình ảnh cho https://doesjitgobrrr.com/).

Tin vui—chúng tôi đã đạt được mục tiêu hiệu suất (rất khiêm tốn) cho CPython JIT sớm hơn một năm đối với macOS AArch64 và sớm vài tháng đối với x86_64 Linux. JIT 3.15 alpha trên macOS AArch64 nhanh hơn 11-12% so với trình thông dịch gọi đuôi và nhanh hơn 5-6% so với trình thông dịch tiêu chuẩn trên x86_64 Linux. Những các con số này là phương tiện hình học và mang tính sơ bộ. Phạm vi thực tế giống như giảm tốc độ 20% đến tăng tốc hơn 100% (bỏ qua điểm chuẩn vi mô unpack_sequence). Chúng tôi chưa có hỗ trợ phân luồng miễn phí thích hợp nhưng chúng tôi đang hướng tới điều đó trong 3.15/3.16. JIT hiện đã hoạt động trở lại.

Tôi không thể nói hết mức độ khó khăn của việc này. Đã có lúc tôi thực sự băn khoăn liệu dự án JIT có tạo ra được những bước tăng tốc có ý nghĩa hay không. Tóm lại, CPython JIT ban đầu thực tế không có phần tăng tốc: 8 tháng trước tôi đã đăng một Bài viết phản ánh JIT về cách CPython JIT ban đầu trong 3.13 và 3.14 thường chậm hơn trình thông dịch. Đó cũng là khoảng thời gian mà nhóm Faster CPython mất nguồn tài trợ từ nhà tài trợ chính. Tôi là một tình nguyện viên nên điều này không ảnh hưởng đến tôi, nhưng quan trọng hơn là nó ảnh hưởng đến những người bạn của tôi làm việc ở đó, và có thời điểm, dường như tương lai của JIT không chắc chắn.

Vậy điều gì đã thay đổi so với 3.13 và 3.14? Tôi sẽ không kể lại câu chuyện hào hùng nào về cách chúng tôi đã cứu JIT khỏi bờ vực thất bại nhờ sự nhạy bén của mình. Tôi thực lòng cho rằng phần lớn thành công hiện tại của chúng tôi là nhờ may mắn – đúng lúc, đúng nơi, đúng người, đúng cược. Tôi thực sự không nghĩ rằng điều này có thể xảy ra nếu một trong những người đóng góp cốt lõi cho JIT: Savannah Ostrowski, Mark Shannon, Diego Russo, Brandt Bucher và tôi không có mặt trong ảnh. Để không loại trừ những người đóng góp tích cực khác cho JIT, tôi cũng sẽ kể tên thêm một vài người: Hai Zhu, Zheaoli, Tomas Roun, Reiden Ong, Donghee Na, và có lẽ tôi còn thiếu một vài người nữa.

Tôi sẽ đề cập đến một phần ít được nhắc đến của JIT: con người và một chút may mắn. Nếu bạn muốn biết chi tiết kỹ thuật về cách chúng tôi thực hiện, hãy tìm tại đây

Nhóm Faster CPython mất nhà tài trợ chính vào năm 2025. Tôi ngay lập tức nổi lên ý tưởng quản lý cộng đồng. Vào thời điểm đó, tôi khá không chắc chắn rằng điều này sẽ hiệu quả. Các dự án JIT không được biết đến là tốt cho những người đóng góp mới. Về mặt lịch sử, nó đòi hỏi rất nhiều kiến thức chuyên môn.

Tại cuộc chạy nước rút cốt lõi CPython ở Cambridge, nhóm cốt lõi JIT đã gặp nhau và chúng tôi đã viết ra một kế hoạch để có JIT nhanh hơn 5% là 3,15 và JIT nhanh hơn 10% là 3,16, với hỗ trợ phân luồng tự do. Một lưu ý phụ, ít gây chú ý hơn nhưng lại quan trọng đối với hoạt động của dự án: đó là giảm hệ số xe buýt. Chúng tôi muốn có 2 người bảo trì tích cực trong cả 3 giai đoạn của JIT; giao diện người dùng (bộ chọn vùng), trung cấp (trình tối ưu hóa), phụ trợ (trình tạo mã).

Trước đây, JIT chỉ có 2 cộng tác viên trung cấp thường xuyên hoạt động. Hiện nay, JIT có 4 cộng tác viên thường xuyên tích cực cho mảng trung cấp và tôi sẽ xem xét 2 nhà phát triển không cốt lõi (Hai Zhu và Reiden) có năng lực và là thành viên được đánh giá cao.

Điều có tác dụng thu hút mọi người là các phương pháp kỹ thuật phần mềm thông thường: chia nhỏ các vấn đề phức tạp thành các phần có thể quản lý được. Brandt đã bắt đầu việc này sớm hơn vào phiên bản 3.14, khi anh mở nhiều các vấn đề lớn giúp phân chia việc tối ưu hóa JIT thành các nhiệm vụ đơn giản. Ví dụ. chúng tôi sẽ nói “thử tối ưu hóa một lệnh duy nhất trong JIT”. Tôi lấy ý tưởng của Brandt và làm điều này với giá 3,15. May mắn thay, tôi đã có một công việc dễ dàng hơn vì vấn đề của tôi liên quan đến việc chuyển đổi các hướng dẫn của trình thông dịch sang một dạng dễ tối ưu hóa. Để khuyến khích những người đóng góp mới, tôi cũng đưa ra các hướng dẫn rất chi tiết có thể áp dụng được ngay lập tức. Tôi cũng phân chia rõ ràng các đơn vị công việc. Tôi nghi ngờ rằng điều đó đã giúp ích vì chúng tôi có 11 cộng tác viên (bao gồm cả tôi) đang giải quyết vấn đề đó, chuyển đổi gần như toàn bộ trình thông dịch sang một thứ gì đó thân thiện hơn với trình tối ưu hóa JIT. Cốt lõi là JIT có thể được chia nhỏ từ một đốm màu mờ đục thành thứ gì đó mà một lập trình viên C không có kinh nghiệm về JIT có thể đóng góp.

Những điều khác cũng có hiệu quả: khuyến khích mọi người, tôn vinh những thành tựu dù lớn hay nhỏ. Mỗi JIT PR đều có kết quả rõ ràng, điều mà tôi nghi ngờ là đã mang lại cho mọi người cảm giác định hướng.

Những nỗ lực tối ưu hóa cộng đồng đã được đền đáp. JIT đã tăng từ nhanh hơn 1% trên x86_64 Linux lên nhanh hơn 3-4% (xem đường màu xanh bên dưới) trong khoảng thời gian đó:

Hiệu suất JIT so với trình thông dịch trong nỗ lực tối ưu hóa cộng đồng (Ảnh ghi công của https://doesjitgobrrr.com/).

Phần 2: Cược may mắn

Ghi lại dấu vết

Một lần nữa, tôi cho rằng điều này phần lớn là do may mắn, nhưng trong các cuộc chạy nước rút lõi CPython ở Cambridge, Brandt mọt sách đã yêu cầu tôi viết lại giao diện người dùng JIT thành giao diện truy tìm. Ban đầu tôi không thích ý tưởng này, nhưng với tư cách là một hình thức phát triển thân thiện theo hướng bất chấp, tôi nghĩ mình sẽ viết lại nó chỉ để chứng minh cho anh ấy thấy nó không hiệu quả.

Nguyên mẫu ban đầu hoạt động trong 3 ngày, tuy nhiên phải mất một tháng để nó hoạt động bình thường mà không bị lỗi trong bộ thử nghiệm. Kết quả ban đầu thật tồi tệ—chậm hơn khoảng 6% trên x86_64 Linux. Tôi đang định bỏ ý định đó thì một tai nạn may mắn đã xảy ra: tôi đã hiểu sai một gợi ý của Mark.

Mark đã đề xuất trước đó là xâu chuỗi bảng điều phối thông qua trình thông dịch, do đó có hai bảng điều phối trong trình thông dịch (một trình thông dịch bình thường và một để theo dõi). Mark gợi ý rằng chúng ta nên có bảng theo dõi là phiên bản theo dõi của các hướng dẫn thông thường. Tuy nhiên, tôi đã hiểu sai và nghĩ ra một phiên bản thậm chí còn cực đoan hơn: thay vì truy tìm các phiên bản của lệnh thông thường, tôi chỉ có một lệnh chịu trách nhiệm truy tìm và tất cả các hướng dẫn trong bảng thứ hai đều chỉ ra điều đó. Có, tôi biết phần này khó hiểu, hy vọng một ngày nào đó tôi sẽ cố gắng giải thích rõ hơn. Điều này hóa ra là một sự lựa chọn thực sự tốt. Tôi nhận thấy rằng cách tiếp cận bảng kép ban đầu chậm hơn rất nhiều do kích thước của trình thông dịch tăng gấp đôi, gây ra tình trạng phình to mã biên dịch và đương nhiên là chậm lại. Bằng cách chỉ sử dụng một lệnh duy nhất và hai bảng, chúng tôi chỉ tăng kích thước trình thông dịch lên 1 lệnh và cũng giữ cho trình thông dịch cơ sở cực nhanh. Tôi trìu mến gọi đây là cơ chế điều phối kép.

Còn rất nhiều điều nữa được đưa vào thiết kế của trình thông dịch ghi dấu vết. Tôi đang thổi kèn cho riêng mình ở đây, nhưng tôi thực sự nghĩ đó là một tác phẩm nghệ thuật nhỏ. Tôi mất 1 tuần để lặp lại trình thông dịch cho đến khi nó nhanh hơn về tổng thể. Nó đi từ chậm hơn 6% đến gần như không tăng tốc sau khi sử dụng công văn kép. Sau đó, tôi loại bỏ một loạt trường hợp chậm trong trình thông dịch theo dõi để cuối cùng làm cho nó nhanh hơn 1,x%. Theo ước tính của riêng tôi, việc truy tìm trình thông dịch chỉ chậm hơn 3-5 lần so với trình thông dịch chuyên dụng. Điểm mấu chốt của vấn đề này là nó tôn trọng mọi hành vi bình thường của phiên dịch viên chuyên nghiệp và hầu như không can thiệp vào điều đó.

Chỉ để cho bạn biết tầm quan trọng của việc ghi lại dấu vết: nó đã tăng mức độ bao phủ của mã JIT lên 50%. Điều này có nghĩa là tất cả các hoạt động tối ưu hóa trong tương lai có thể sẽ kém hiệu quả hơn khoảng 50% (giả sử tất cả các mã đều thực thi giống nhau, điều này tất nhiên là không đúng, xin vui lòng thông cảm cho tôi :).

Vì vậy, tôi phải cảm ơn Brandt và Mark vì đã dẫn tôi đến với một giải pháp hay như vậy.

Loại bỏ số lượng tham chiếu

Việc đặt cược may mắn khác mà chúng tôi đã thực hiện sớm là thử loại bỏ số lượng tham chiếu. Một lần nữa, đây là công việc ban đầu của Matt Page được thực hiện trong trình tối ưu hóa mã byte CPython (chi tiết hơn trong bài đăng blog trước về tối ưu hóa). Tôi nhận thấy rằng vẫn còn một nhánh trong mã JITted cho mỗi lần giảm số lượng tham chiếu ngay cả khi trình tối ưu hóa mã byte hoạt động. Tôi nghĩ: “tại sao không thử loại bỏ nhánh cây đó đi”, và không biết nó sẽ giúp ích được bao nhiêu. Hóa ra một chi nhánh thực sự khá đắt tiền và chúng sẽ tăng lên theo thời gian. Đặc biệt nếu mỗi lệnh Python có >=1 nhánh!

Điều may mắn còn lại là việc song song hóa dễ dàng và đây là một 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ính năng tối ưu hóa chính mà chúng tôi đã hướng dẫn mọi người thực hiện trong JIT Python 3.15. Mặc dù đây chủ yếu là quy trình tái cấu trúc thủ công nhưng nó đã dạy cho mọi người những phần quan trọng họ cần để tìm hiểu về JIT mà không khiến họ choáng ngợp.

Phần 3: Một đội ngũ tuyệt vời

Chúng tôi có đội ngũ cơ sở hạ tầng tuyệt vời. Tôi nói điều này một phần là đùa, vì đó là một người. Trên thực tế, “đội” của chúng tôi hiện có 4 chiếc máy đang chạy trong tủ của Savannah. Tuy nhiên, Savannah đã thực hiện công việc tương đương với toàn bộ nhóm cơ sở hạ tầng cho JIT. JIT không thể tiến triển nhanh như vậy nếu chúng tôi không có gì để báo cáo các con số hiệu quả hoạt động của mình. Các lần chạy JIT hàng ngày đã góp phần thay đổi cuộc chơi trong vòng phản hồi. Nó đã giúp chúng tôi nắm bắt được sự hồi quy về hiệu suất JIT và cho chúng tôi biết các hoạt động tối ưu hóa của chúng tôi thực sự có hiệu quả.

Mark rất xuất sắc về mặt kỹ thuật và tôi nghĩ anh ấy biết Internet đã khen ngợi anh ấy quá nhiều nên tôi sẽ không nói gì thêm ở đây :).

Diego cũng rất tuyệt. Anh ấy chịu trách nhiệm về JIT trên phần cứng ARM và gần đây cũng đã bắt đầu nỗ lực làm cho JIT trở nên thân thiện với người lập hồ sơ. Tôi không thể nói quá mức độ khó của vấn đề này.

Brandt đã đặt nền tảng ban đầu cho chương trình phụ trợ mã máy của chúng tôi, nếu không có nó, chúng tôi sẽ có những cộng tác viên mới viết trình biên dịch mã, điều này có thể sẽ khiến nhiều người thất vọng hơn.

Phần 4: Nói chuyện với mọi người

Tôi cũng muốn khuyến khích ý tưởng nói chuyện với mọi người và chia sẻ ý tưởng.

Xin gửi lời cảm ơn tới CF Bolz-Tereick, người đã dạy tôi rất nhiều điều về PyPy. Tôi đã dành vài tháng để xem mã nguồn của PyPy và tôi tin rằng điều này đã giúp tôi trở thành một nhà phát triển JIT giỏi hơn về tổng thể. CF rất hữu ích khi tôi cần giúp đỡ.

Tôi cũng tham gia cuộc trò chuyện thân thiện với người biên soạn với Max Bernstein, nếu không có cuộc trò chuyện này thì tôi có thể đã mất động lực cho việc này từ lâu rồi. Max là một nhà văn viết giỏi và là một người biên soạn thân thiện.

Ý tưởng không tồn tại trong một căn phòng kín. Tôi nghi ngờ rằng mình đã viết JIT giỏi hơn nhờ đi chơi với nhiều người biên dịch một thời gian. Ít nhất, nhìn vào PyPy đã mở rộng tầm nhìn của tôi!

Kết luận

Con người rất quan trọng và nếu may mắn, JIT sẽ thành công.

Tác giả: guidoiaquinti

#discussion