gh-116167: Cho phép vô hiệu hóa GIL
(github.com/python)- CPython PR #116338 đã merge vào
python:mainthay đổi cho phép vô hiệu hóa GIL trong free-threaded build bằngPYTHON_GIL=0hoặc-X gil=0 - Để chừa khả năng bật lại GIL trong runtime, các cấu trúc dữ liệu liên quan đến GIL vẫn được khởi tạo như bình thường; việc vô hiệu hóa được xử lý bằng cách đặt flag khi khởi động để
take_gil()vàdrop_gil()trả về sớm - Trong kiểm tra ban đầu, với thiết lập
PYTHON_GIL=0, một số test và chương trình nhỏ không dùng thread hoạt động bình thường; các chương trình thread rất cơ bản đôi khi hoạt động, nhưng toàn bộ test suite nhanh chóng crash ởtest_asyncio - Trong quá trình review, các test cho
PYTHON_GIL, tài liệu, tùy chọn-X gil, và phản ánh trongsys.flagsđã được bổ sung; phần xử lý thiết lập cũng được sửa đểPYTHON_GIL=1buộc bật GIL - Các công việc tiếp theo được tách thành vấn đề bật lại GIL khi load extension không tương thích và vấn đề mặc định vô hiệu hóa GIL; thay đổi này bổ sung bề mặt điều khiển GIL trong free-threaded build của Python 3.13
Thay đổi đã được merge
- CPython PR #116338 xử lý thay đổi
gh-116167: Allow disabling the GIL with PYTHON_GIL=0 or -X gil=0 colesburyđã merge vàopython:mainngày 11/03/2024- Quy mô thay đổi được hiển thị là 12 file, thêm 163 dòng, xóa 1 dòng
- Tính năng nhắm tới không phải build thông thường, mà là tùy chọn chạy để vô hiệu hóa GIL trong free-threaded build
Cách vô hiệu hóa GIL
- Trong free-threaded build, có thể vô hiệu hóa GIL bằng các thiết lập sau
PYTHON_GIL=0-X gil=0
- Để có thể bật lại GIL trong runtime, mọi cấu trúc dữ liệu liên quan đến GIL đều được khởi tạo như bình thường
- Việc vô hiệu hóa thực tế được thực hiện bằng cách đặt flag khi khởi động
- Vì flag này,
take_gil()vàdrop_gil()trả về sớm
- Vì flag này,
- Trong quá trình review, một commit thiết lập đúng
enable_gilkhiPYTHON_GIL=1cũng được bổ sung
Test và các hạn chế hiện tại
- Một số test và chương trình nhỏ đã được kiểm tra với thiết lập
PYTHON_GIL=0- Các test và chương trình nhỏ không dùng thread được xác nhận là hoạt động bình thường
- Các chương trình thread rất cơ bản đôi khi hoạt động
- Toàn bộ test suite đã crash nhanh chóng, và vị trí được ghi nhận là
test_asyncio - Bằng lệnh
!buildbot nogil, các bài test builder liên quan đến NoGIL đã được lên lịch nhiều lầnx86-64 MacOS Intel ASAN NoGIL PRx86-64 MacOS Intel NoGIL PRARM64 MacOS M1 Refleaks NoGIL PRARM64 MacOS M1 NoGIL PRAMD64 Ubuntu NoGIL Refleaks PRAMD64 Ubuntu NoGIL PRAMD64 Windows Server 2022 NoGIL PR
Phạm vi được bổ sung trong quá trình review
corona10đề xuất rằng việc thêm test biến môi trường vàoLib/test/test_cmd_line.pylà đáng làm- Sau đó các commit sau đã được bổ sung
Add test for PYTHON_GIL in test_cmd_lineSet enable_gil properly when PYTHON_GIL=1Don't add 'enable_gil' to test_embed in normal builds
colesburycho rằng nên viết tài liệu tại thời điểm thêm biến môi trường- Lý do được nêu là flag configure
--disable-gilđã được tài liệu hóa - Nội dung tài liệu cần nêu rằng chỉ dùng được trong free-threaded build,
0buộc vô hiệu hóa GIL,1buộc bật GIL, và đây là điểm mới của Python 3.13
- Lý do được nêu là flag configure
- Sau đó commit
Document PYTHON_GIL environment variableđã được bổ sung
Bổ sung tùy chọn -X gil và merge cuối cùng
- Sau thảo luận trên Discord, nhóm quyết định bổ sung cả tùy chọn
-Xđể dùng cùng biến môi trường - Tiêu đề PR được đổi từ dạng chỉ đề cập
PYTHON_GIL=0sang bao gồm cảPYTHON_GIL=0 or -X gil=0 - Các commit bổ sung bao gồm nội dung sau
Add -X gil option, add to sys.flags, modify test to cover env var… and optionFix link to -X gilFix PYTHON_GIL versionchanged lineClarify test_flags in normal builds
ericsnowcurrently,erlend-aasland,corona10,colesburyđã phê duyệt thay đổi- Merge commit là
2731913, và sau khi merge,vstinnerphản ứng rằng thay đổi này “thú vị và rất đáng sợ”
Công việc tiếp theo
- Hai công việc được tách thành các issue tiếp theo
- PR hiện tại không phải là thay đổi giá trị mặc định của GIL, mà là thay đổi cho phép người dùng điều khiển trạng thái GIL bằng biến môi trường hoặc tùy chọn
-Xtrong free-threaded build
1 bình luận
Ý kiến trên Hacker News
Để dành cho những ai tò mò về công việc no-GIL, tôi để thêm vài liên kết: [0], [1]
[0] Multithreaded Python without the GIL
https://docs.google.com/document/d/18CXhDb1ygxg-YXNBJNzfzZsD...
[1] Github repo
https://github.com/colesbury/nogil
[0] https://peps.python.org/pep-0703/
[1] https://github.com/colesbury/nogil-3.12
Tôi rất mong chờ xem có thể làm Python gốc nhanh hơn đến mức nào. Có quá nhiều công cụ đang cố giảm nhẹ vấn đề đó đến mức đề xuất giá trị của Python cũng đang bị thách thức
Các công cụ cải thiện tốc độ mà tôi nghĩ tới gồm Mojo, pytorch, triton, numba, taichi. Có quá nhiều nỗ lực đang cố giải bài toán này nên lần trước khi tôi định thử một cái, số lựa chọn nhiều đến mức choáng ngợp. Cuối cùng tôi chọn taichi, và nó khá thú vị, dễ dùng, nhưng phạm vi áp dụng hơi hạn chế
Taichi thực sự đang bị đánh giá thấp. Nó chạy trên mọi nền tảng, gồm cả Metal, có rất nhiều ví dụ, và cũng dễ viết code. Quan trọng nhất là nó tích hợp với hệ sinh thái thay vì thay thế hệ sinh thái hiện có
https://github.com/taichi-dev
Video demo rất hay cho thấy có thể làm gì với Taichi: https://www.youtube.com/watch?v=oXRJoQGCYFg
https://www.youtube.com/watch?v=WNh4Q7-OSJs
https://www.taichi-lang.org/
Tôi thắc mắc vì sao cách đếm tham chiếu thiên lệch được mô tả tại https://peps.python.org/pep-0703/ lại chỉ có tính thân thiện với một luồng, còn nếu truy cập từ luồng khác thì lại cần tăng/giảm nguyên tử
Ở các triển khai khác, ví dụ nhiều crate Rust có triển khai đếm tham chiếu thiên lệch, tôi từng thấy cách làm là chỉ tăng nguyên tử khi chuyển sang luồng mới, rồi luồng đó sẽ tăng/giảm không nguyên tử cho tới khi về lại 0, sau đó mới thực hiện một lần giảm nguyên tử cuối cùng. Không rõ có phải vì đây là kiểu chắp thêm vào hệ thống hiện có nên chỉ có một PyObject duy nhất và không thể thay thế nó để trỏ sang một đối tượng cục bộ theo luồng mới hay không
Trong Rust, thao tác "move" để chuyển quyền sở hữu là một phần của ngôn ngữ, nhưng trong C hay Python thì không có khái niệm tương ứng, nên khó xác định khi nào nên chuyển quyền sở hữu và luồng nào nên trở thành chủ sở hữu mới. Có thể dùng heuristic. Ví dụ, khi đưa một đối tượng vào queue.SimpleQueue thì có thể từ bỏ hoặc chuyển quyền sở hữu, nhưng ngay cả khi đó cũng khó biết trước luồng nào sẽ
getđối tượng khỏi hàng đợiLợi ích về hiệu năng có lẽ cũng không lớn. Nhiều đối tượng chỉ được truy cập từ một luồng, một số đối tượng được truy cập từ nhiều luồng, nhưng những đối tượng chỉ được truy cập độc quyền bởi một luồng rồi sau đó lại chỉ được truy cập độc quyền bởi một luồng khác thì khá hiếm
Lúc đầu tôi đọc tin về bánh mì cắt lát theo đợt, giờ lại thêm cả chuyện này nữa à? Đúng là thời đại đáng kinh ngạc
Hồi dự án Unladen Swallow [1] lụi tàn, tôi cũng hơi tiếc. Thật vui khi thấy Python quay lại con đường tối ưu hóa cốt lõi
[1] https://en.wikipedia.org/wiki/CPython#Unladen_Swallow
Giá mà ai đó giải thích kiểu như cho trẻ 5 tuổi thì tốt
Tôi hiểu GIL là gì về mặt khái niệm. Nhưng tác động của thay đổi này là gì? Có phải giờ chúng ta sẽ kỳ vọng hiệu năng tổng thể tăng lên, đồng thời các package bắt đầu hỏng không?
Ngay cả khi không phải tác vụ CPU nặng, thay đổi này vẫn có thể hữu ích. Gần đây, rất nhiều mã được viết bằng tính năng ngôn ngữ asyncio gốc của Python. Nó hoạt động trên một luồng đơn bằng cách nhường quyền thực thi qua async/await, giống như NodeJS, và chỉ với một luồng cũng có thể đạt thông lượng khá tốt ở mức hàng nghìn request mỗi giây
Nhưng vấn đề lớn là chỉ cần bạn thực hiện bất kỳ tác vụ CPU nào thì nó sẽ chặn mọi coroutine khác, gây ra đủ loại vấn đề mơ hồ và làm hỏng số request mỗi giây. Ví dụ, bạn có thể thấy timeout I/O ngẫu nhiên trong một coroutine, nhưng nguyên nhân thực tế lại là một coroutine hoàn toàn khác đã chiếm CPU trong chốc lát. Cũng rất khó quan sát được vì sao chuyện này xảy ra. asyncio có cung cấp hàm
asyncio.to_thread()[1] để giúp đưa tác vụ chặn ra ngoài luồng chính, nhưng vì GIL, nó không thể thật sự cô lập tác vụ thiên về CPU để chúng không can thiệp vào các coroutine khác[1] https://docs.python.org/3/library/asyncio-task.html#asyncio....
Dành cho ai đang thắc mắc, GIL là viết tắt của Global Interpreter Lock
Có tài liệu nào tóm tắt bức tranh lớn ở đây tốt hơn không?
Cuối cùng thì cũng có thể mong chờ các benchmark giữa nhiều công cụ