Hướng dẫn backblaze python gil - backblaze python gil

Nhóm của chúng tôi đã có một số thử nghiệm thú vị với Python 3,9-Nogil, kết quả sẽ được báo cáo trong một bài đăng trên blog sắp tới. Trong khi đó, chúng tôi đã thấy một cơ hội để đi sâu hơn vào lịch sử của Lock phiên dịch toàn cầu [GIL], bao gồm lý do tại sao nó làm cho Python rất dễ dàng tích hợp và sự đánh đổi giữa dễ dàng và hiệu suất.

& nbsp; Chúng tôi đã liên hệ với Barry Warsaw, một nhà phát triển và đóng góp Python ưu việt, bởi vì chúng tôi không thể nghĩ ra ai tốt hơn để phá vỡ sự phát triển của Gil cho chúng tôi. Barry là một nhà phát triển Python Core lâu năm, cựu quản lý phát hành và thành viên hội đồng chỉ đạo, và thành viên PSF. Ông là người dẫn đầu dự án cho người quản lý danh sách gửi thư GNU Mailman. Barry, cùng với người đóng góp Paweł Polewicz, một nhà phát triển phần mềm phụ trợ và người dùng Python lâu năm, đã vượt lên trên và vượt ra ngoài bất cứ điều gì chúng ta có thể tưởng tượng, phát triển sự lặn sâu toàn diện này vào Gil và sự tiến hóa của nó trong những năm qua. Cũng xin cảm ơn Larry Hastings vì đã xem xét và phản hồi của mình.

& nbsp; Nếu Python xông Gil là thứ bạn tò mò, chúng tôi rất thích nghe suy nghĩ của bạn trong các bình luận. Chúng tôi sẽ để Barry lấy nó từ đây.

& nbsp;

Điều đầu tiên đầu tiên: Gil là gì?

Python Gil, hay khóa thông dịch viên toàn cầu, là một cơ chế trong CPython [triển khai phổ biến nhất của Python] phục vụ để tuần tự hóa các hoạt động liên quan đến trình thông dịch mã Bytepy Python và cung cấp bảo đảm an toàn hữu ích cho trạng thái đối tượng và thông dịch viên bên trong. Trong khi cung cấp nhiều lợi ích, như các cuộc thảo luận dưới đây sẽ cho thấy, Gil cũng ngăn CPython đạt được hiệu suất đa lõi đầy đủ.

Nói một cách đơn giản nhất, GIL là một khóa [hoặc mutex] chỉ cho phép một luồng hệ điều hành duy nhất chạy vòng thông dịch mã python byte trung tâm. Thông thường, khi nhiều luồng có thể truy cập trạng thái được chia sẻ, chẳng hạn như trình thông dịch toàn cầu hoặc trạng thái nội bộ đối tượng, một lập trình viên sẽ cần thực hiện các khóa hạt mịn để ngăn một luồng dậm trên trạng thái được đặt bởi một luồng khác. Gil loại bỏ sự cần thiết của các khóa hạt mịn này vì nó áp đặt một khóa toàn cầu ngăn nhiều luồng đột biến trạng thái này cùng một lúc.

Trong bài đăng này, tôi sẽ khám phá những ưu và nhược điểm của Gil, và nhiều nỗ lực trong nhiều năm qua để loại bỏ nó, bao gồm một số phát triển thú vị gần đây.

Khởi đầu khiêm tốn

Trở lại vào tháng 11 năm 1994, tôi đã được mời đến một chút tập hợp của những người đam mê ngôn ngữ lập trình để gặp nhà phát minh Hà Lan của một ngôn ngữ đối tượng tương đối mới và ít được biết đến. Hội thảo ba ngày này được tổ chức bởi bạn bè của tôi và các đồng nghiệp cũ tại Viện Tiêu chuẩn và Công nghệ Quốc gia [NIST] tại Gaithersburg, MD. Tôi đã đến với nhiều kinh nghiệm về các ngôn ngữ từ C, C ++, Forth, Lisp, Perl, TCL và Objective-C và thích học và chơi với các ngôn ngữ lập trình mới.

Tất nhiên, nhà phát minh người Hà Lan là Guido Van Rossum và ngôn ngữ nhỏ của anh ta là Python. Tôi nghĩ rằng hầu hết chúng ta tham dự đều biết có điều gì đó đặc biệt về Python và Guido, nhưng có lẽ chúng ta sẽ sốc khi biết rằng Python thậm chí sẽ có khoảng 30 năm sau, chứ đừng nói đến phạm vi, tác động hoặc sự nổi tiếng mà nó thích ngày hôm nay . Đối với cá nhân tôi, đó là một khoảnh khắc thay đổi cuộc sống.

Vài năm trước, tôi đã nói chuyện tại Baypiggies đã có cái nhìn hồi tưởng về sự phát triển của Python từ phiên bản 1.1 vào tháng 10 năm 1994 [ngay trước hội thảo đã nêu trên], thông qua loạt phim Python 2, và cho đến Python 3.7 của ngôn ngữ tại thời điểm đó. Theo nhiều cách, Python 1.1 sẽ có thể nhận ra bởi lập trình viên Python hiện đại ngày nay. Theo những cách khác, bạn đã tự hỏi làm thế nào Python có thể sử dụng được mà không có các tính năng được giới thiệu trong những năm qua.

Bạn có thể tưởng tượng không có các tích hợp tuple[] hoặc list[], hoặc tài liệu, hoặc ngoại lệ lớp, đối số từ khóa, *args, **kws, các gói hoặc thậm chí các toán tử khác nhau để kiểm tra phân công và bình đẳng không? Thật thú vị khi quay trở lại với tất cả những thay đổi cũ đó và hãy nhớ nó giống như thế nào vì mỗi tính năng chúng ta hiện đang được giới thiệu, thường là trong những ngày đầu không quan tâm đến khả năng tương thích ngược.

Tôi đã tìm được chương trình nghị sự cho hội thảo Python đầu tiên đó và một trong những mục được thảo luận là cải thiện hiệu quả của Python [ví dụ: bằng cách sử dụng sơ đồ thu gom rác khác]. Tôi không nhớ bất kỳ chi tiết nào của cuộc thảo luận đó, nhưng ngay cả khi đó, và ngay từ khi bắt đầu, Python đã sử dụng một kế hoạch quản lý bộ nhớ đếm tham chiếu [máy dò rác tuần hoàn đã ở lại nhiều năm]. Đếm tham chiếu là một cách đơn giản để quản lý các đối tượng của bạn bằng ngôn ngữ cấp cao hơn, nơi bạn không phân bổ trực tiếp hoặc giải phóng bộ nhớ của bạn. Một trong những nguyên tắc hướng dẫn ban đầu của Guido, cho Python, và đã phục vụ Python trong nhiều năm qua, là giữ cho nó đơn giản nhất có thể trong khi vẫn có hiệu quả, hữu ích và vui vẻ.

Những điều cơ bản của việc đếm tham chiếu

Đếm tham chiếu là đơn giản; Như đã nói trên hộp thiếc, trình thông dịch giữ một bộ đếm theo dõi mọi tham chiếu đến một đối tượng. Ví dụ: liên kết một đối tượng với một biến [chẳng hạn như bằng một gán] làm tăng số lượng tham chiếu đối tượng đó lên một. Việc nối một đối tượng vào danh sách cũng tăng số lượng tham chiếu của nó lên một. Loại bỏ một đối tượng khỏi danh sách làm giảm số lượng tham chiếu của đối tượng đó. Khi một biến đi ra khỏi phạm vi, số lượng tham chiếu của đối tượng, biến bị ràng buộc sẽ bị giảm một lần nữa. Chúng tôi gọi đây là tính toán tham chiếu này, số lượng đối tượng của bạn đã giới thiệu và hai hoạt động này lần lượt là các hoạt động và các sắc lệnh của Dec Decref.

Khi một đối tượng REFCOUNT chuyển sang 0, điều đó có nghĩa là không còn tham chiếu trực tiếp nào đến đối tượng, vì vậy nó có thể được giải phóng một cách an toàn [và hoàn thiện] bởi vì không có gì trong chương trình có thể đạt đến đối tượng đó nữa1. Vì các đối tượng này được giải quyết, bất kỳ tài liệu tham khảo nào về các đối tượng mà chúng nắm giữ cũng là sắc lệnh, v.v. Việc giới thiệu lại cho người phiên dịch Python một cơ chế rất đơn giản để giải phóng rác và quan trọng hơn, nó cho phép con người suy luận về quản lý bộ nhớ của Python, cả từ quan điểm của lập trình viên Python, và từ quan điểm thuận lợi của người viết mở rộng C, người Không có sự xa xỉ của tất cả việc đếm tham chiếu đó sẽ tự động xảy ra.

Đây là một điểm quan trọng: khi chúng ta nói về trò chơi Py Python, chúng ta thường có nghĩa là Cpython, thì việc thực hiện thời gian chạy được viết bằng C2. Lập trình viên C làm việc trong thời gian chạy CPython và tác giả mô -đun viết các tiện ích mở rộng cho Python trong C [để thực hiện hoặc tích hợp với một số thư viện hệ thống] phải lo lắng về tất cả các chi tiết nghiêm khắc của thời điểm khi nào có thể hoặc quyết định một đối tượng. Nhận sai điều này và tiện ích mở rộng của bạn có thể rò rỉ bộ nhớ hoặc nhân đôi một đối tượng miễn phí, bằng cách nào đó đang tàn phá hệ thống của bạn. May mắn thay, Python có các quy tắc rõ ràng để tuân theo và tài liệu tốt, nhưng vẫn có thể khó có thể giới thiệu ngay trong các tình huống phức tạp, chẳng hạn như khi xử lý lỗi thích hợp dẫn đến nhiều đường dẫn thoát khỏi hàm.

Ở đây, nơi mà Gil đến: Đếm và đồng thời tham khảo

Một trong những quy tắc đơn giản hóa quan trọng là lập trình viên không phải lo lắng về sự đồng thời khi quản lý đếm tham chiếu Python. Hãy suy nghĩ về tình huống bạn có nhiều luồng, mỗi luồng chèn và xóa một đối tượng Python khỏi một bộ sưu tập như danh sách hoặc từ điển. Bởi vì những chủ đề đó có thể chạy bất cứ lúc nào và theo bất kỳ thứ tự nào, bạn thường phải cực kỳ phòng thủ trong cách bạn incref và ra lệnh cho các đối tượng đó, và sẽ quá dễ dàng để hiểu sai điều này. Bạn có thể làm hỏng Python, hoặc tệ hơn, nếu bạn không thực hiện các khóa thích hợp xung quanh các hoạt động Incref và Decref của bạn. Phải lo lắng về tất cả những gì sẽ làm cho mã C của bạn rất phức tạp và có khả năng khá dễ bị lỗi. Việc triển khai CPython cũng có các biến toàn cầu và tĩnh dễ bị tổn thương bởi các điều kiện chủng tộc3.

Để phù hợp với các nguyên tắc của Python, vào năm 1992, khi Guido lần đầu tiên bắt đầu thực hiện hỗ trợ luồng trong Python, ông đã sử dụng một cơ chế đơn giản để giữ cho điều này có thể quản lý được cho một loạt các lập trình viên Python và tác giả mở rộng: một người phiên dịch toàn cầu khóa Gil khét tiếng!

Vì bản thân trình thông dịch Python không an toàn cho luồng, Gil chỉ cho phép một luồng thực thi mã byte Python tại một thời điểm và do đó tuần tự hóa tất cả các truy cập vào các đối tượng Python. Vì vậy, các lỗi cấm, nhiều luồng không thể dẫm lên các hoạt động đếm tham chiếu của nhau. Có các chức năng A API để phát hành và thu được GIL xung quanh việc chặn I/O hoặc tính toán các chức năng chuyên sâu mà don lồng chạm vào các đối tượng Python, và chúng cung cấp các ranh giới cho người phiên dịch để chuyển sang các chủ đề thực hiện python khác.

Hai chủ đề tăng lên một bộ đếm tham chiếu đối tượng.

Do đó, chúng tôi có được sự đơn giản thực hiện C đáng kể với chi phí của một số song song. Python hiện đại có nhiều cách để làm việc xung quanh giới hạn này, từ asyncio đến các quy trình con và đa xử lý, tất cả đều hoạt động tốt nếu chúng phù hợp với yêu cầu của bạn. Python cũng bề mặt các nguyên thủy của hệ điều hành, nhưng chúng có thể tận dụng tối đa các hoạt động đa lõi vì GIL.

Ưu điểm của Gil

Quay trở lại những ngày đầu của Python, chúng tôi đã không có sự phổ biến của các bộ xử lý đa lõi, vì vậy tất cả đều hoạt động tốt. Ngày nay, các ngôn ngữ lập trình hiện đại thân thiện với nhiều người hơn và Gil có một bản rap tồi. Trước khi chúng tôi khám phá công việc để loại bỏ Gil, điều quan trọng là phải hiểu mức độ lợi ích và số dặm của Python đã thoát khỏi nó.

Một khía cạnh quan trọng của GIL là nó đơn giản hóa mô hình lập trình cho các tác giả mô -đun mở rộng. Khi viết các mô-đun mở rộng trong C, C ++ hoặc bất kỳ ngôn ngữ cấp thấp nào khác có quyền truy cập vào phần bên trong của thông dịch viên Python, các tác giả mở rộng thường phải đảm bảo rằng không có điều kiện chủng tộc nào có thể làm hỏng trạng thái bên trong của các đối tượng Python. Đồng thời rất khó để có được quyền, đặc biệt là trong các ngôn ngữ cấp thấp và một sai lầm có thể làm hỏng toàn bộ trạng thái của phiên dịch. Đối với một tác giả mở rộng, có thể rất khó khăn để đảm bảo tất cả các sắc tố và sắc lệnh của bạn được cân bằng đúng, đặc biệt là đối với bất kỳ chi nhánh, lối thoát nào hoặc điều kiện lỗi, và điều này sẽ khó khăn hơn nếu tác giả cũng phải tranh cãi với việc thực hiện đồng thời . GIL cung cấp một mô hình đơn giản hóa quan trọng về quyền truy cập đối tượng [bao gồm cả thao tác refCount] vì nó đảm bảo rằng chỉ một luồng thực thi có thể biến đổi các đối tượng Python tại một thời gian5.

Có những lợi ích hiệu suất quan trọng của GIL cho các hoạt động đơn luồng là tốt. Nếu không có Gil, Python sẽ cần một số cách khác để đảm bảo rằng các refcount đối tượng an toàn khỏi tham nhũng do, ví dụ, điều kiện chủng tộc giữa các luồng, chẳng hạn như khi thêm hoặc loại bỏ các đối tượng khỏi bất kỳ bộ sưu tập có thể thay đổi nào [danh sách, từ điển, bộ] chia sẻ trên các chủ đề. Những kỹ thuật này có thể rất tốn kém như một số thí nghiệm được mô tả sau đó cho thấy. Đảm bảo rằng trình thông dịch Python an toàn cho các trường hợp sử dụng đa luồng làm giảm hiệu suất của nó cho trường hợp sử dụng đơn. Chi phí hiệu suất thấp của Gil, thực sự tỏa sáng cho các hoạt động đơn luồng, bao gồm các chương trình I/O-Multiplexed trong đó các thư viện thích Asyncio được sử dụng và đây vẫn là việc sử dụng Python chiếm ưu thế. Các khóa hạt mịn hơn cũng làm tăng cơ hội bế tắc, điều này không thể xảy ra với Gil.

Ngoài ra, một trong những lý do Python rất phổ biến ngày nay là vì nó có rất nhiều phần mở rộng được viết cho nó trong những năm qua. Một trong những lý do có rất nhiều mô -đun mở rộng mạnh mẽ, cho dù chúng tôi có muốn thừa nhận hay không, là GIL làm cho các phần mở rộng đó dễ viết hơn.

Tuy nhiên, các lập trình viên Python từ lâu đã mơ ước có thể chạy các chương trình Python đa luồng để tận dụng tối đa tất cả các lõi có sẵn trên các nền tảng điện toán hiện đại. Ngay cả ngày nay, đồng hồ và điện thoại có nhiều lõi, trong khi ở Python, những ngày đầu, các hệ thống đa lõi là rất hiếm. Ở đây chúng tôi khoảng 30 năm sau, và trong khi Gil đã phục vụ tốt, để tận dụng những gì rõ ràng dường như nhiều hơn một mốt nhất thời, Python, Gil thường có được sự đồng thời đa năng hiệu suất cao thực sự.

Cố gắng loại bỏ Gil

Hai chủ đề tăng bộ đếm tham chiếu đối tượng mà không cần bảo vệ Gil.

Trong những năm qua, nhiều nỗ lực đã được thực hiện để loại bỏ Gil.

1999: Greg Stein xông vào Chủ đề miễn phí

Circa 1999, công việc của Greg Stein xông vào chủ đề miễn phí là một trong những nỗ lực đầu tiên [thành công!] Để loại bỏ Gil. Nó làm cho các khóa hạt mịn hơn nhiều và di chuyển các biến toàn cầu bên trong trình thông dịch thành một cấu trúc, mà chúng ta thực sự vẫn sử dụng cho đến ngày nay. Tuy nhiên, nó có tác dụng phụ đáng tiếc, làm cho mã Python của bạn chậm hơn nhiều lần. Do đó, trong khi công việc luồng miễn phí là một thử nghiệm tuyệt vời, nó quá không thực tế để áp dụng.

2015: Larry Hasting sườn Gilectomy

Nhiều năm sau [khoảng năm 2015], dự án Gilectomy đã được đặt tên tuyệt vời đã thử một cách tiếp cận khác để loại bỏ Gil. Trong cuộc nói chuyện của Larry, Pycon 2016, anh ấy thảo luận về bốn cân nhắc kỹ thuật phải được giải quyết khi loại bỏ Gil:

  1. Đếm tham chiếu: Điều kiện đua về cập nhật refcount giữa nhiều luồng như được mô tả trước đây.
  2. Globals và Statics: Chúng bao gồm các biến số thông dịch viên toàn cầu và các đối tượng singleton được chia sẻ. Nhiều công việc đã được thực hiện trong những năm qua để chuyển các thế giới này vào các cấu trúc chủ đề. Eric Snow, làm việc trên nhiều phiên dịch viên [hay còn gọi là nhóm Sub SubPreters] cũng đã đạt được rất nhiều tiến bộ trong việc cách ly các biến này thành các cấu trúc đại diện cho một phiên bản phiên dịch, trong đó về mặt lý thuyết, mỗi trường hợp có thể chạy trên một lõi riêng biệt. Thậm chí còn có những đề xuất để làm cho một số đối tượng singleton được chia sẻ bất tử, do đó các điều kiện đếm cuộc tham chiếu sẽ không ảnh hưởng đến tuổi thọ của các đối tượng đó. Một đề xuất liên quan thú vị sẽ chuyển GIL vào cấu trúc dữ liệu trên mỗi inter, điều này có thể dẫn đến khả năng chạy một phiên bản phiên dịch bị cô lập trên mỗi lõi [với các hạn chế].
  3. C Extensions: Keep in mind that there is a huge ecosystem of C extension modules, and much of Python’s power comes from these extension modules, of which NumPy is a hugely popular example. These extensions have never had to worry about parallelism or re-entrancy because they’ve always relied on the GIL to serialize their operations. At a minimum, a GIL-less Python will require recompilation of extension modules, and some or all may require some level of source code modifications as well. These changes may include protecting internal [non-Python] data structures for concurrency, using functional APIs for refcount modification instead of accessing refcount fields directly, not assuming that Python collections are stable over iteration, etc.
  4. Atomicity: Operations such as adding or deleting objects from Python collections such as lists and dictionaries actually involve a number of steps internally. To the Python developer, these all appear to be atomic operations, and in fact they are, thanks to the GIL.

Larry also identifies what he calls three “political” considerations, but which I think are more in the realm of the social contract between Python developers and Python users:

  1. Removing the GIL should not hurt performance for single-threaded or I/O-bound multithreaded code.
  2. We can’t break existing C extensions as described above6.
  3. Don’t let GIL removal make the CPython interpreter too complicated or difficult to understand. One of Guido’s guiding principles, and a subtle reason for Python’s huge success, is that even with complicated features such as exception handling, asyncio, generators, etc. Python’s C core is still relatively easy to learn and understand. This makes it easy for new contributors to engage with Python core development, an absolutely essential quality if you want your language to thrive and grow for its next 30 years as much as it has for its previous 30.

Larry’s Gilectomy work is quite impressive, and I highly recommend watching any of his PyCon talks for deep technical dives, served with a healthy dose of humor. As Larry points out, removing the GIL isn’t actually the hard part. The hard part is doing so while adhering to the above mentioned technical and social constraints, retaining Python’s single-threaded performance, and building a mechanism that scales with the number of cores. This latter constraint is important because if we’re going to enable multicore operations, we want to ensure that Python’s performance doesn’t hit a plateau at four or eight cores.

So, why did the Gilectomy branch fail [measured in units of “didn’t get adopted by CPython”]? For the most part, the performance and complexity constraints couldn’t be met. One of the biggest hits on performance wasn’t actually lock contention on objects. The early Gilectomy work relied on atomic increment and decrement CPU instructions, which destroyed cache consistency, and caused a high overhead of communication on the intercore bus to ensure atomicity.

Intercore atomic incr/decr communication.

Later, Larry experimented with a technique borrowed from garbage collection research called “buffered reference counting,” essentially a transaction log for refcount changes. However, contention on transaction logs required further modifications to segregate logs by threads and by increment and decrement operations. This led to non-realtime garbage collection events on refcounts reaching zero, which broke features such as Python’s weakref objects.

Interestingly, another hotspot turned out to be what’s called “obmalloc,” which is a small block allocator that improves performance over just using system malloc for everything. We’ll touch on this again later. Solving all these knock-on effects [such as repairing the cyclic garbage collector] led to increased complexity of the implementation, making the chance that it would ever get merged into Python highly unlikely.

Before we leave this topic to look at some new and exciting work, let’s return briefly to Eric Snow’s work on multiple interpreters [aka subinterpreters]. PEP 554 proposes to add a new standard library module called “interpreters” which would expose the underlying work that Eric has been doing to isolate interpreter state out of global variables internal to CPython. One such global state is, of course, the GIL. With or without Python-level access to these features, if the GIL could be moved from global state to per-interpreter state, each interpreter instance could theoretically run concurrently with the others. You could therefore attach a different interpreter instance to each thread, and these could run Python code in parallel. This is definitely a work in progress and it’s unclear whether multiple interpreters will deliver on its promises of this kind of limited concurrency. I say “limited” because without full GIL removal, there is significant complexity in sharing Python objects between interpreters, which would almost certainly be necessary. Issues such as ownership [which thread owns which object] and safe mutability would need to be resolved. PEP 554 proposes some solutions to these problems and more, so we’ll have to keep an eye on this work. But even multiple interpreters don’t provide the same true concurrency that full GIL removal promises.

Tương lai của Gil: Chúng ta sẽ đi đâu từ đây?

Và bây giờ chúng ta có vòng tròn đầy đủ, bởi vì sự phổ biến, ảnh hưởng lớn và tầm với của Python cũng là một trong những lý do tại sao dường như vẫn không thể loại bỏ GIL trong khi vẫn giữ được hiệu suất đơn luồng và không phá vỡ toàn bộ hệ sinh thái của các mô-đun mở rộng.

Tuy nhiên, ở đây, chúng tôi đang ở với Pycon 2022 vừa kết thúc, và có sự phấn khích mới đối với công việc của Sam Gross trong thời gian Nogil Nogil, trong đó có lời hứa về một Cpython không có người biểu diễn với sự không tương thích ngược tối thiểu ở cả hai lớp Python và C. Mặc dù một số hồi quy hiệu suất là không thể tránh khỏi, công việc của Sam, cũng sử dụng một số kỹ thuật thông minh để vuốt các hồi quy này trở lại thông qua các cải tiến hiệu suất nội bộ khác.

Hai chủ đề tăng tốc độ tham chiếu đối tượng trên Sam Gross trong thời gian Nogil.

Với những cải tiến này cũng như công việc mà nhóm Guido, tại Microsoft đang thực hiện với dự án Cpython nhanh hơn của mình, có hy vọng và sự phấn khích mới có thể bị xóa trong khi giữ lại hoặc thậm chí cải thiện hiệu suất tổng thể và không từ bỏ khả năng tương thích ngược. Nó rõ ràng sẽ là một nỗ lực nhiều năm.

Dự án Sam Nog Nogil nhằm mục đích hỗ trợ một điểm ngọt ngào đồng thời. Nó hứa hẹn rằng các điều kiện đua dữ liệu sẽ không bao giờ làm hỏng máy ảo Python, nhưng nó để lại tính toàn vẹn của các cấu trúc dữ liệu cấp người dùng cho lập trình viên. Đồng thời là khó khăn, và nhiều chương trình và thư viện Python được hưởng lợi từ các ràng buộc Gil ngầm, nhưng giải quyết đây là một vấn đề khó khăn hơn ngoài phạm vi của dự án Nogil. Các ứng dụng khoa học dữ liệu là một miền tiềm năng lớn được hưởng lợi từ bộ xử lý đa bộ xử lý thực sự trong Python.

Có một số kỹ thuật mà dự án Nogil sử dụng để loại bỏ nút cổ chai GIL. Như đã đề cập, dự án cũng sử dụng một số cải tiến máy ảo khác để lấy lại một số hiệu suất không thể tránh khỏi bằng cách loại bỏ Gil. Tôi đã giành chiến thắng trong quá nhiều chi tiết về những cải tiến này, nhưng nó rất hữu ích khi lưu ý rằng nơi này độc lập với Nogil, họ có thể và đang được điều tra cùng với các công việc khác mà nhóm Guido, đang làm để cải thiện hiệu suất chung của CPython.

Python 3.11 gần đây đã nhập beta [và do đó có tính năng Freeze], và với nó, chúng tôi sẽ thấy những cải tiến hiệu suất đáng kể, điều này không còn nghi ngờ gì nữa trong các bản phát hành Python trong tương lai. Khi nào và nếu Nogil được thông qua, một số lợi ích hiệu suất đó có thể thoái lui để hỗ trợ Nogil. Cho dù và làm thế nào điều này sẽ là một sự đánh đổi tốt sẽ là một điểm phân tích và tranh luận thú vị trong những năm tới. Trong bài báo gốc của Sam, ông đề xuất một công tắc thời gian chạy để lựa chọn giữa Nogil và hoạt động Gil bình thường, tuy nhiên điều này đã được thảo luận tại Hội nghị thượng đỉnh ngôn ngữ Pycon 2022, và sự đồng thuận là điều này sẽ thực tế. Do đó, khi thử nghiệm Nogil di chuyển về phía trước, nó sẽ được bật bởi một công tắc thời gian biên dịch.

Ở cấp độ cao, việc loại bỏ GIL được cung cấp bởi các thay đổi trong ba lĩnh vực: bộ phân bổ bộ nhớ, đếm tham chiếu và bảo vệ thu thập đồng thời. Mỗi trong số này là những chủ đề sâu sắc của riêng họ, vì vậy chúng tôi sẽ chỉ có thể chạm vào chúng một cách ngắn gọn.

Nogil Phần 1: Bộ phân bổ bộ nhớ

Bởi vì mọi thứ trong Python là một đối tượng và hầu hết các đối tượng được phân bổ động trên đống, trình thông dịch CPython thực hiện một số cấp độ phân bổ bộ nhớ và cung cấp các hàm CI C để phân bổ và giải phóng bộ nhớ. Điều này cho phép nó phân bổ hiệu quả các khối bộ nhớ thô từ hệ điều hành và phân chia và quản lý các khối đó dựa trên loại đối tượng được đặt vào chúng. Ví dụ, các số nguyên có các yêu cầu bộ nhớ khác nhau so với từ điển, do đó, có Trình quản lý bộ nhớ dành riêng cho đối tượng đối với các loại đối tượng này [và các đối tượng khác] làm cho quản lý bộ nhớ bên trong trình thông dịch hiệu quả hơn nhiều.

Cpython cũng sử dụng một bộ phân bổ đối tượng nhỏ, được gọi là pymalloc, giúp cải thiện hiệu suất để phân bổ và giải phóng các đối tượng nhỏ hơn hoặc bằng 512 byte. Điều này chỉ chạm vào sự phức tạp của quản lý bộ nhớ bên trong trình thông dịch. Điểm của tất cả sự phức tạp này là cho phép tạo và phá hủy đối tượng hiệu quả hơn, nhưng nó cũng cho phép các tính năng như gỡ lỗi phân bổ bộ nhớ và phân bổ bộ nhớ tùy chỉnh.

Nogil Works tận dụng khả năng cắm này để sử dụng một bộ phân bổ bộ nhớ an toàn chủ đề, hiệu quả cao, hiệu quả cao được phát triển bởi Daan Leijen tại Microsoft có tên là Mimalloc. Bản thân Mimalloc xứng đáng với một cái nhìn sâu sắc, nhưng với mục đích của chúng tôi, nó đủ để biết rằng thiết kế Mimalloc được điều chỉnh cực kỳ tốt để phân bổ các khối bộ nhớ hiệu quả và an toàn. Dự án Nogil sử dụng các cấu trúc này để triển khai từ điển và các loại thu thập khác nhằm giảm thiểu nhu cầu khóa trên quyền truy cập không đột nhập, cũng như quản lý các đối tượng thu thập rác7 với sổ sách kế toán tối thiểu. Mimalloc cũng đã được điều chỉnh cao cho hiệu suất và an toàn chủ đề.

Nogil Phần 2: Đếm tham chiếu

Nogil cũng thực hiện một số thay đổi để đếm tham chiếu, mặc dù nó làm như vậy một cách thông minh để giảm thiểu các thay đổi đối với API C hạn chế, nhưng không bảo toàn ABI ổn định. Điều này có nghĩa là trong khi các mô -đun mở rộng phải được biên dịch lại, mã nguồn của chúng có thể không yêu cầu sửa đổi, bên ngoài một vài trường hợp góc đã biết8.

Một ý tưởng rất hứa hẹn là làm cho một số đối tượng trở nên bất tử một cách hiệu quả, mà tôi đã chạm vào trước đó. True, False, None và một số đối tượng khác trong thực tế không bao giờ thực sự thấy các bản tóm tắt của chúng về số 0, và vì vậy chúng vẫn sống trong suốt cuộc đời của quá trình Python. Bằng cách sử dụng các bit ít có ý nghĩa nhất của trường đếm tham chiếu đối tượng để ghi sổ, Nogil có thể làm cho các macro không có sự tham gia cho các đối tượng này, do đó tránh mọi sự tranh chấp trên các luồng cho các trường này.

Nogil sử dụng một hình thức tham chiếu thiên vị đếm để chia một đối tượng REFCOUNT thành hai thùng. Đối với các thay đổi refcount trong chuỗi sở hữu đối tượng, các thay đổi của địa phương này có thể được thực hiện bằng các dạng thông thường [không nguyên tử] hiệu quả hơn. Để thay đổi việc giới thiệu các đối tượng trong một luồng khác, một hoạt động nguyên tử là cần thiết để sửa đổi đồng thời an toàn của một bản thu hồi chia sẻ trên mạng. Chủ đề sở hữu đối tượng sau đó có thể kết hợp refcount địa phương và chia sẻ này cho mục đích thu gom rác, và nó có thể từ bỏ quyền sở hữu khi refcount địa phương của nó về số 0. Điều này được thực hiện khi hầu hết các truy cập đối tượng là cục bộ vào chủ đề sở hữu, thường là trường hợp. Sơ đồ đếm tham chiếu thiên vị của Nogil có thể sử dụng các nhóm bộ nhớ Mimalloc, để theo dõi hiệu quả các chủ đề sở hữu.This is performant when most object accesses are local to the owning thread, which is generally the case. nogil’s biased reference counting scheme can utilize mimalloc’s memory pools to efficiently keep track of the owning threads.

Tuy nhiên, một số đối tượng thường thuộc sở hữu của nhiều luồng và không bất tử và đối với các loại đối tượng này [ví dụ: các chức năng, mô -đun], một sơ đồ đếm tham chiếu hoãn lại được sử dụng. Incref và Decref hoạt động như bình thường đối với các đối tượng này, nhưng khi trình thông dịch tải các đối tượng này lên ngăn xếp bên trong của nó, các bản tin không được sửa đổi. Tiện ích của kỹ thuật này được giới hạn trong các đối tượng chỉ được giải quyết trong quá trình thu gom rác vì chúng thường tham gia vào các chu kỳ tham chiếu.

Bộ thu gom rác cũng được sửa đổi để đảm bảo rằng nó chỉ chạy tại các điểm ranh giới an toàn, chẳng hạn như ranh giới thực thi mã byte. Việc triển khai Nogil hiện tại của Bộ sưu tập rác là một luồng đơn và dừng thế giới, vì vậy nó an toàn cho luồng. Nó tái sử dụng một số hàm A API hiện tại để đảm bảo rằng nó không chờ đợi trên các chủ đề bị chặn trên I/O.

Nogil Phần 3: Bảo vệ bộ sưu tập đồng thời

Kỹ thuật cấp cao thứ ba mà Nogil sử dụng để cho phép đồng thời là thực hiện một thuật toán hiệu quả để khóa các đối tượng container, chẳng hạn như từ điển và danh sách, khi đột biến chúng. Để duy trì an toàn chủ đề, ở đó, không có cách nào để sử dụng khóa cho việc này. Tuy nhiên, Nogil tối ưu hóa cho các đối tượng được sửa đổi chủ yếu trong một luồng duy nhất và nó thừa nhận rằng các đối tượng thường xuyên và được sửa đổi đồng thời có thể cần một thiết kế khác.

Giấy Nogil của Sam đi sâu vào chi tiết đáng kể về thuật toán khóa, nhưng ở cấp độ cao, nó phụ thuộc vào phiên bản container [trong đó mọi sửa đổi đối với một container đều va vào một phiên bản phiên bản để các truy cập đọc khác nhau có thể biết liệu container có được sửa đổi giữa Đọc hay không], đếm tham chiếu thiên vị và các tính năng Mimalloc khác nhau để tối ưu hóa cho theo dõi nhanh, một luồng đơn, không đọc sửa đổi trong khi khấu hao chi phí khóa để ghi chống lại các hoạt động đắt tiền khác, một hoạt động ghi hộp đựng điển hình áp đặt.

Lời cuối cùng và một số dự đoán

Dự án Sam Gross Nog Nogil rất ấn tượng. Anh ta đã xoay sở để đáp ứng hầu hết các ràng buộc khó khăn đã cản trở các nỗ lực trước đó trong việc loại bỏ GIL, bao gồm giảm thiểu càng nhiều càng tốt để tác động đến hiệu suất một luồng [và giao dịch cải thiện hiệu suất thông dịch viên cho chi phí loại bỏ Gil], duy trì [duy trì [ Hầu hết] Khả năng tương thích ngược API của Python để không buộc thay đổi trên toàn bộ hệ sinh thái mô -đun mở rộng và trong suốt thời gian này [mặc dù độ dài của bài viết này!] Bảo tồn khả năng đọc và khả năng hiểu của trình thông dịch CPython.

Bạn không còn nghi ngờ gì nữa nhận thấy rằng lỗ thỏ đi khá sâu và chúng tôi chỉ khám phá một số đường hầm trong hang đặc biệt này. May mắn thay, việc thực hiện ngữ nghĩa của Python và Cpython đã được ghi nhận rõ ràng trong vòng đời 30 năm của nó, vì vậy có rất nhiều cơ hội để tự khám phá và đóng góp! Nó sẽ có sự tham gia bền vững thông qua các bước cẩn thận và gia tăng để đưa những ý tưởng này đến kết quả. Tương lai chắc chắn là thú vị.

Nếu tôi phải đoán, tôi sẽ nói rằng chúng tôi sẽ thấy các tính năng như nhiều phiên dịch viên cung cấp một số giá trị đồng thời trong bản phát hành tiếp theo hoặc lâu hơn, với việc xóa Gil năm năm [và do đó, năm bản phát hành] trở lên. Tuy nhiên, nhiều kỹ thuật được mô tả ở đây đã được thử nghiệm và có thể xuất hiện sớm hơn. Python 3.11 sẽ có nhiều cải tiến hiệu suất đáng chú ý, với nhiều chỗ cho công việc hiệu suất bổ sung trong các bản phát hành trong tương lai. Những điều này sẽ cung cấp cho phòng làm việc của Nogil để tiếp tục thử nghiệm với hiệu suất đa lõi thực sự.

Đối với một ngôn ngữ và thông dịch viên đã đi từ một nhóm nhỏ những người đam mê may mắn và có sẵn cho một ngôn ngữ lập trình hàng đầu trên toàn thế giới, tôi nghĩ rằng có nhiều sự phấn khích và lạc quan hơn cho tương lai của Python. Và điều đó thậm chí không nói về những người thay đổi trò chơi như Pyscript.

Hãy theo dõi một bài đăng giới thiệu các thí nghiệm hiệu suất mà nhóm Backblaze đã thực hiện với bộ lưu trữ đám mây Python 3.9-Nogil và Backblaze B2. Bạn đã thử nghiệm với Python 3,9-Nogil chưa? Hãy cho chúng tôi biết trong các ý kiến.

Barry Warsaw

Barry đã là một nhà phát triển cốt lõi Python từ năm 1994 và được liệt kê là người đóng góp không phải là người đầu tiên cho Python. Ông đã làm việc với nhà phát minh Python, Guido Van Rossum, tại CNRI khi Guido và Python Development, chuyển từ Hà Lan đến Hoa Kỳ. Ông là một người quản lý phát hành Python và thành viên Hội đồng chỉ đạo, được tạo ra và đặt tên cho quá trình Python tăng cường [PEP], và có liên quan đến sự phát triển của Python cho đến ngày nay. Ông là người lãnh đạo dự án cho GNU Mailman, và trong một thời gian đã duy trì Jython, việc thực hiện Python được xây dựng trên JVM. Ông hiện là một kỹ sư nhân viên cao cấp tại LinkedIn, một người chơi bass bán chuyên nghiệp và người đam mê Tai Chi. Tất cả các ý kiến ​​và bình luận thể hiện trong bài viết này là của riêng ông.

Liên lạc với Barry:

  • LinkedIn
  • barry.warsaw.us
  • Twitter [@pumpichank]

Paweł Polewicz

Pawel đã là nhà phát triển phụ trợ từ năm 2002. Ông đã xây dựng nhà ga E-Radio lớn nhất hành tinh vào năm 2006-2007, làm quản lý QA trong sáu năm, và cuối cùng, đã bắt đầu rạn san hô, một nhà phần mềm chuyên xây dựng Python phụ trợ Python cho các công ty khởi nghiệp.

Hãy liên lạc với Paweł:

  • LinkedIn
  • reef-technologies.com

Twitter [@pumpichank]

  1. Paweł Polewicz
  2. Pawel đã là nhà phát triển phụ trợ từ năm 2002. Ông đã xây dựng nhà ga E-Radio lớn nhất hành tinh vào năm 2006-2007, làm quản lý QA trong sáu năm, và cuối cùng, đã bắt đầu rạn san hô, một nhà phần mềm chuyên xây dựng Python phụ trợ Python cho các công ty khởi nghiệp.
  3. Hãy liên lạc với Paweł:
  4. Ghi chú
  5. Các chu kỳ tham chiếu không chỉ có thể mà còn phổ biến đáng ngạc nhiên, và những điều này có thể giữ cho đồ thị của các đối tượng không thể truy cập được sống vô thời hạn. Python 2.0 đã thêm một người thu gom rác tuần hoàn thế hệ để xử lý những trường hợp này. Các chi tiết rất khó khăn và xứng đáng với một bài viết theo đúng nghĩa của nó.
  6. CPYThon còn được gọi là triển khai tham chiếu của người Viking vì các tính năng mới xuất hiện ở đó trước tiên, mặc dù chúng được xác định cho ngôn ngữ Python chung chung. Nó cũng là triển khai phổ biến nhất, và điển hình là những gì mọi người nghĩ đến khi họ nói về Python.
  7. Nhiều công việc đã được thực hiện trong những năm qua để giảm những điều này càng nhiều càng tốt.
  8. Nó thậm chí còn tồi tệ hơn điều này ngụ ý. Việc gỡ lỗi các vấn đề đồng thời là khó khăn vì các điều kiện dẫn đến lỗi gần như không thể sao chép và một số công cụ tồn tại để giúp đỡ.
  9. Mã đồng thời thiết bị để cố gắng nắm bắt hành vi có thể giới thiệu sự khác biệt về thời gian tinh tế để che giấu vấn đề. Ngành công nghiệp thậm chí đã đặt ra thuật ngữ này, Heisenbug, để mô tả sự phức tạp của lớp lỗi này.

Bài Viết Liên Quan

Chủ Đề