
The GIL Is Not a Lock on Your CPU
Eight threads of pure-Python arithmetic run at 0.98x the speed of one. The same eight threads doing IO run at 7.90x. The GIL is a scheduler, and it has a dial.
Elliot Sayer
Python, its runtimes, frameworks, and the systems beneath them — read from source, measured, not assumed.

Eight threads of pure-Python arithmetic run at 0.98x the speed of one. The same eight threads doing IO run at 7.90x. The GIL is a scheduler, and it has a dial.

Annotations cost nothing per call and everything at import. Reading __annotations__ takes 24 ns; resolving the same class with get_type_hints takes 3.4 µs, 140x more.

Removing Django's entire default middleware chain saved 0.02 ms per request. Fixing one N+1 query on the same view saved 3.30 ms.

Awaiting a coroutine that never suspends costs 53 ns against 24 ns for a plain call. Wrapping the same coroutine in a Task and awaiting it costs 28,958 ns.

Importing asyncio costs 21.9 ms and pulls in 100 modules. Importing email costs 0.1 ms and pulls in one. Granularity predicts import cost; size does not.

An instance with three attributes costs 96 bytes, not the 344 that sys.getsizeof reports, and __slots__ makes attribute reads slower on CPython 3.13.