
- Single-threaded: 2.75 seconds
- Multi-threaded: 14.3 seconds (not a typo!)
- Multi-processing: 8.7 seconds
In other words, for this scenario, the CPython no-GIL multithreaded version beats even PyPy at its most optimal. As yet, there is no build of CPython that enables the JIT and uses free threading, but such a version is not far away and could easily change the picture even further.
Conclusion
In sum, PyPy running the most basic, unoptimized version of a math-heavy script still outperforms CPython. But CPython gets drastic relative improvements from using free-threading and even multiprocessing, where possible.
While PyPy cannot take advantage of those built-in features, its base speed is fast enough that using threading or multiprocessing for some jobs isn’t really required. For instance, the n-body problem is hard to parallelize well, and computing pi can hardly be parallelized at all, so it’s a boon to be able to run single-threaded versions of those algorithms fast.

