Janis Lesinskis' Blog

Assorted ramblings

  • All entries
  • About me
  • Projects
  • Economics
  • Misc
  • Software-engineering
  • Sports

GIL removal now on standards track


A big shift in priorities has been happening with the CPython implementation of the Python programming language in the last few years. For a long time performance related work was not a high priority in the CPython implementation but times are changing as Python has become more popular in the data science and machine learning space. Recently developers have spent a lot of time trying to speed up CPython. For example in early 2024 these changes were merged into CPython that introduced a JIT compiler, see PEP 744 for more details.

As far as performance goes the Global Interpreter Lock is the big ticket item for CPython performance when concurrency is involved. The GIL is an implementation detail from the early days of Python that unfortunately has severe performance impacts on some types of concurrent code. In previous versions of CPython the GIL created the limitation that only one thread could execute Python code at a given time. The new Python 3.13 beta has some significant changes like the ability to disable the GIL and introduces a JIT compiler as mentioned earlier.

When Python was released to the public in 1991 the hardware at the time was significantly less powerful compared to today. In particular CPUs back then typically had a single core and concurrency wasn't anywhere near as widespread in application code as it is now. The design goals of Python has always been correctness first and speed second, and it probably made a lot of sense that the time to introduce a lock into the interpreter itself to ensure that certain operations could be performed safely without risking corrupting the internal state of the interpreter itself. While it would have been possible to implement an interpreter without needing these locks it would have been far harder to achieve this. When Python was first being developed the tradeoff of effort required vs benefits from introducing better concurrency looked vastly different. Back in the early 1990s far more code was single threaded as far more hardware didn't support concurrency like it does today. Investing that extra time and effort back then just wouldn't have been worth it.

However times change and as time has gone by there has been increasingly large amounts of computing power made available in affordable chips. More and more software is written that takes advantage of the great increases in computational power that's available in modern hardware, often doing so by leveraging concurrency in smart ways. Unfortunately for CPython early design choices led to a case of path dependence, decisions early on to spend more development time on things like expanding the standard library have taken focus away from performance and concurrency support. These early choices to get Python useable are what made Python so popular, back in the early 1990s the lack of a comprehensive standard library was an enormous hinderance to getting work done. Keep in mind that package management was far less developed back then and popular sites like Github.com didn't exist. My early experiences with writing C++ and the accompanying pain of not having easy library options for a variety of tasks was what made me look into Python, when I needed to do one-off work having a language suited to that was perfect. Then some time later Python became popular in machine learning and data science and the performance requirements from the user base shifted considerably. Unfortunately CPython just isn't a fast interpreter, and workloads had changed that started to expose this as a bottleneck. Changing highly popular languages which have a lot of code already running on them is a hard task, especially so when you consider that core developer time is extremely valuable and extremely scarce. In recent years a number of Python Enhancement Proposals, or PEPs as they are abbreviated, have emerged that are now directly targeting performance of the interpreter itself.

The reason PEP 703 is so important is that it's starting to rectify the threaded performance problems in CPython. Retrofitting these changes is now a much more difficult task than if the language were newer because a lot of old code depends on the GIL existing in some form or another. For example consider this code:

l = []

def append_an_item_to_end(item):
    l.append(item)

In the past the function append_an_item_to_end would only reliably have worked in a multi-threaded environment because of the GIL. When .append is called the interpreter has a lock that's held that does things like increase the size of the underlying storage and change the length of the list. (If you want to do this sort of thing properly in a multi threaded environment use should use a queue instead). While this code is both simple and correct in a multithreaded environment if the GIL is enabled this correctness can come at an extreme penalty in terms of performance. Generally speaking the best thing to do is to use data structures that are thread safe from the beginning and have no need for the GIL to be there to be correct, but even then you still have to be careful. There's a lot of ways to work around these performance pitfalls but the language experience would be nicer if those workarounds were not needed as often.

When you write multithreaded code you can easily run into situations where the GIL just kills your performance because of locks being held in the interpreter itself. Now with Python 3.13 you can run free threaded mode by building the interpreter with the option --disable-gil.

Why not just remove the GIL?

Before getting into this topic we have to recognize that concurrent implementations of programming languages are really hard to get right. To retrofit one to a language that didn't have it beforehand is an even harder task if you are trying to not break existing code. For quite some time removing the GIL from the CPython implementation has been in the "too hard" basket, the rewards were insufficient for the amount of effort required. But as mentioned earlier the times are changing, people have more concurrency support in their hardware than ever before. Big companies have made steps to move from Python to other languages because the performance isn't sufficient for their needs.

From the early days of CPython the general advice has been to write code that runs in the Python interpreter focusing only on correctness then only if you run into performance problems when running that code in production should you profile the code and start to optimize it. This typically has meant that people have wrote C-extensions for Python, which are external libraries callable from within Python that are written directly in C. While these C extensions can dramatically speed up code it means that they are using the C API for the Python implementation directly. Unfortunately CPython is quite slow as far as languages go and this is why projects like PyPy exist in the first place.

Many of the older C extensions are written in such a way that they assume that the GIL exists. In some senses the GIL is an implementation detail, as it's not mentioned in the Python language itself, but practically speaking many programmers have produced code that directly depends on this implementation detail. As such removing the GIL could therefore break some older code. This means that removing the GIL might require a major version bump to show that backwards compatibility might be broken. The last time this happened with Python 3 the bump to a new major version was quite disruptive and a lot of places held on to Python 2 for exceedingly long periods of time1. I can definitely sense a hesitancy to do another Python major version bump and it will probably take a few backwards compatibility breaking changes to be proposed together to make the case stronger for a new major version.

For a while now work has been ongoing to prepare CPython to remove the GIL, this is a large task. With PEP 703 we see a major breakthrough, this proposal is now on the standards track and it will start the long process of disabling the GIL becoming the default option. Now that this is now on the standards track I decided to write this post.

Will the removal of the GIL effectively be Python 4? Some people are calling for a major version bump because some C old extensions will break. How much code breaks because of concurrency assumptions made based on legacy python implementations will be interesting to see for sure! Chances are that some changes won't break much compared with others.


  1. And much like people who still run windows XP boxes today, I fully understand that the costs of migration can be very high if you have some software that already does everything you need it to do. If you depend on external libraries that didn't get updated you end up in this bad spot where you have to decide on upgrading or not where the choice to upgrade might mean you lose significant functionality in your software or face expensive decisions to reimplement it. ↩

Published: Wed 08 May 2024
By Janis Lesinskis
In Software-engineering
Tags: python mypy type-systems API-design Cpython backwards-compatibility PEP703 PEP744 global-interpreter-lock

links

  • Amazon wishlist

social

  • My GitHub page
  • LinkedIn

Proudly powered by Pelican, which takes great advantage of Python.