1 điểm bởi GN⁺ 2024-03-12 | 1 bình luận | Chia sẻ qua WhatsApp
  • CPython PR #116338 đã merge vào python:main thay đổi cho phép vô hiệu hóa GIL trong free-threaded build bằng PYTHON_GIL=0 hoặ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()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 trong sys.flags đã được bổ sung; phần xử lý thiết lập cũng được sửa để PYTHON_GIL=1 buộ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ào python:main ngà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()drop_gil() trả về sớm
  • Trong quá trình review, một commit thiết lập đúng enable_gil khi PYTHON_GIL=1 cũ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ần
    • x86-64 MacOS Intel ASAN NoGIL PR
    • x86-64 MacOS Intel NoGIL PR
    • ARM64 MacOS M1 Refleaks NoGIL PR
    • ARM64 MacOS M1 NoGIL PR
    • AMD64 Ubuntu NoGIL Refleaks PR
    • AMD64 Ubuntu NoGIL PR
    • AMD64 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ào Lib/test/test_cmd_line.py là đáng làm
  • Sau đó các commit sau đã được bổ sung
    • Add test for PYTHON_GIL in test_cmd_line
    • Set enable_gil properly when PYTHON_GIL=1
    • Don't add 'enable_gil' to test_embed in normal builds
  • colesbury cho 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, 0 buộc vô hiệu hóa GIL, 1 buộc bật GIL, và đây là điểm mới của Python 3.13
  • 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=0 sang 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 option
    • Fix link to -X gil
    • Fix PYTHON_GIL versionchanged line
    • Clarify test_flags in normal builds
  • ericsnowcurrently, erlend-aasland, corona10, colesbury đã phê duyệt thay đổi
  • Merge commit là 2731913, và sau khi merge, vstinner phả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
    • #116322: công việc bật lại GIL khi load extension không tương thích
    • #116329: công việc mặc định vô hiệu hóa GIL
  • 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 -X trong free-threaded build

1 bình luận

 
GN⁺ 2024-03-12
Ý 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

  • 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ế

  • 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

    • Có thể trong tương lai CPython sẽ triển khai chuyển giao quyền sở hữu, nhưng việc đó phức tạp hơn một chút
      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 đợi
      Lợ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?

    • Trước đây, vì GIL, người ta hầu như không viết Python đa luồng thực sự. Luồng chủ yếu được dùng để xử lý nhiều tác vụ có thể bị chặn bởi I/O độc lập, điều này dĩ nhiên phổ biến và hữu ích, nhưng không giúp cải thiện hiệu năng của mã Python thiên về CPU
      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....
    • Nếu package nào phụ thuộc vào GIL thì GIL sẽ được bật. Package sẽ không bị hỏng
  • 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ụ