August 5, 2026
Threads finally stopped throwing hands
Scaling NumPy on Free-Threaded Python
Python’s big speed glow-up has fans cheering, skeptics rolling their eyes, and nerds fighting in public
TLDR: NumPy fixed several hidden slowdowns so Python threads can better use multiple CPU cores for heavy math work. Commenters split between celebrating the practical speed boost, joking that “all software is a hack,” and diving into a very internet argument over whether the fix was elegant enough.
Python’s favorite number-crunching tool, NumPy, just got a major multi-core makeover on a special version of Python that can finally let threads run at the same time. The blog post walks through why a user’s threaded code was bizarrely losing to a process-based version, even though the work should have split neatly across CPU cores. The culprit? A parade of tiny hidden slowdowns: locks, shared counters, and memory handling getting in each other’s way. The fix was less “grand rewrite” and more “death by a thousand tiny cuts” in reverse—small changes, big payoff.
But the real action is in the peanut gallery. One camp was straight-up delighted: “Nice to see the performance improvements work,” as pjmlp put it, while another praised the post itself for being unusually readable and was almost shocked that a Stack Overflow question kicked off something this serious. That wholesome energy lasted about five seconds before the classic internet scuffle arrived. One commenter dismissed Python as “hacky,” only to get smacked with the practical-reality comeback: who cares if it’s messy if it gets work done? Then came the ultra-nerd subplot, where people started arguing over whether a lock was even needed at all—proof that no performance story is complete until someone turns it into a philosophy seminar.
The vibe? Equal parts victory lap, existential debate, and “please continue fighting, I’m learning things.”
Key Points
- •The article examines work in NumPy and CPython to improve multithreaded scaling on free-threaded CPython for CPU-bound numerical workloads.
- •The investigation was triggered by a StackOverflow report, tracked as numpy/numpy#30494, where ThreadPoolExecutor was significantly slower than ProcessPoolExecutor on a NumPy ufunc-heavy benchmark.
- •In the reported reproducer, each worker processed separate array chunks with repeated `np.sin` and `np.cos` calls and no shared mutable state, yet multithreading was up to 2x slower than multiprocessing.
- •Profiling with samply showed three main classes of bottlenecks on free-threaded Python: lock contention, reference count contention on shared global objects, and memory allocator contention.
- •One documented fix changed CPython's tracemalloc behavior so disabled tracemalloc uses an atomic, lock-free fast path instead of taking a global lock on every allocation and deallocation.