{"article":{"slug":"how-fast-is-python-3-15","title":"How fast is Python 3.15?","subtitle":null,"summary":"Miguel Grinberg runs his informal Fibonacci and bubble-sort benchmark on Python 3.15.0rc3 against 3.10 through 3.14, JIT and free-threaded builds, PyPy and other languages. His take: 3.15 is at best a minor speed improvement over 3.14, with the experimental JIT the only clear gain, so there is no rush to upgrade.","content_type":"blog_post","language":"en","canonical_url":"https://blog.miguelgrinberg.com/post/how-fast-is-python-3-15","author":{"name":"Miguel Grinberg","url":null,"person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"miguelgrinberg.com","url":"https://blog.miguelgrinberg.com/","listing_slug":null,"listing":null},"topics":[{"name":"Python","slug":"python","url":"https://listedarticles.com/topics/python"},{"name":"Performance","slug":"performance","url":"https://listedarticles.com/topics/performance"},{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":2289,"reading_minutes":10,"published_at":"2026-10-05T00:00:00.000Z","added_at":"2026-10-07T02:07:37.783Z","updated_at":"2026-10-07T02:07:37.783Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/how-fast-is-python-3-15","markdown_url":"https://listedarticles.com/articles/how-fast-is-python-3-15.md","example":false,"citation":"Miguel Grinberg, miguelgrinberg.com. \"How fast is Python 3.15?.\" 5 Oct 2026. https://blog.miguelgrinberg.com/post/how-fast-is-python-3-15 (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://blog.miguelgrinberg.com/post/how-fast-is-python-3-15"},"body_markdown":"It's October once again, and that means it is time to take the new release of Python for a spin (technically, it is the 3.15.0rc3 release that I'm using, the official 3.15 release is still a few days out). As I did with my [Python 3.14 performance](https://blog.miguelgrinberg.com/post/python-3-14-is-here-how-fast-is-it) article of a year ago, today I'm sharing a new run of my informal Python benchmark, comparing Python 3.15 against previous interpreters all the way back to 3.10.\n\nIf you are not interested in the charts and the tables and just want to read my analysis, feel free to jump to the [conclusions](https://blog.miguelgrinberg.com/post/how-fast-is-python-3-15#conclusions) section at the end.\n\n## The benchmark\n\nI just called my benchmark \"informal\". What does that mean?\n\nGetting an objective and universal measure of the performance of a programming language is impossible. All you can do is write some programs and run them to get a measure of their performance. Other programs may show similar performance characteristics or they may not, there is really no way to know. My intention with this benchmark is just to get a feel for the performance changes across versions of Python, but I want to make it clear that I'm not trying to obtain a comprehensive performance profile of the Python interpreter.\n\nFor my benchmark I will be running two programs called *fibo.py* and *bubble.py*, which [you can inspect](https://github.com/miguelgrinberg/pyspeed) if you like. These are the same programs I used in past editions of this benchmark. The first calculates numbers from the [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_sequence), and the second sorts numbers using the [bubble sort](https://en.wikipedia.org/wiki/Bubble_sort) algorithm.\n\nI've chosen these two programs as representative of two classes of algorithms. The Fibonacci calculation is done using [recursion](<https://en.wikipedia.org/wiki/Recursion_(computer_science)>), which I have found to be somewhat inefficient in Python interpreters. On the other side, the bubble sort only uses for-loops, without any recursion. There are other types of programs that my benchmark does not attempt to cover. In particular, note that I'm not including [I/O bound](https://en.wikipedia.org/wiki/I/O_bound) code in this benchmark.\n\nBecause some of the performance improvements in recent Python versions revolve around multi-threading, I also created a multi-threaded variation for each program, so in total I have four different tests.\n\n## The testing matrix\n\nThe complete testing matrix is actually fairly complex, because I have to run the four program variations under all the Python versions, plus the JIT and free-threading alternatives for those that have them. I like to run the tests under PyPy as well, because this interpreter has shown impressive performance in past runs of this benchmark. And to place Python performance within the wider ecosystem, I've also ported the two programs to JavaScript (Node.js) and Rust.\n\nHere is the full test matrix that I've worked with:\n\n- 2 test scripts\n  - *fibo.py* : calculates Fibonacci numbers, with recursion\n  - *bubble.py* : sorts a list of randomly generated numbers, without recursion\n- 2 threading modes\n  - Single-threaded\n  - 4 parallel threads\n- 6 Python versions, plus recent versions of PyPy, Node.js and Rust:\n- 3 Python interpreters\n  - Standard\n  - Just-In-Time (JIT): only for CPython 3.13+\n  - Free-threading (FT): only for CPython 3.13+\n\nReaders of my previous benchmarks may recall that I had an additional dimension in my matrix for Linux vs. macOS. Given that there were no significant differences between them in the two previous runs of the benchmark, I've decided to drop the macOS tests this time around, so all tests were executed on my Linux laptop, which has an Intel Core i5 CPU and runs Gentoo Linux.\n\nThe method I'm using to measure the performance of each participant in this benchmark is to run the test program three times and take the average duration of the three. In the tables of results that I share below I also show the speed difference versus the 3.15 version, and when it makes sense also the speed difference versus the previous version of a given interpreter. For speed comparisons I'm using a simple ratio, where 1x means same speed, 0.5x means half speed (or that it took twice the time to run), 2x means twice as fast (or that it ran in half the time if you prefer), etc. Hopefully this makes sense.\n\n## Test 1: fibo.py, single-threaded\n\nLet's get started. The first test calculates the first 40 Fibonacci numbers.\n\n| fibo(40) - 1 thread | Time (secs) | vs. 3.15 | vs. previous | \n|---|---|---|---|\n| 3.10 | 15.9442 | 0.44x |  | \n| 3.11 | 9.4058 | 0.74x | 1.70x | \n| 3.12 | 8.8545 | 0.78x | 1.06x | \n| 3.13 | 8.8174 | 0.79x | 1.00x | \n| 3.14 | 7.1659 | 0.97x | 1.23x | \n| 3.15 | 6.9361 |  | 1.03x | \n| pypy3.12 | 1.2517 | 5.54x |  | \n| node-26.3 | 1.3899 | 4.99x |  | \n| rust-1.97 | 0.0898 | 77.24x |  | \n\nBelow you can see the above speeds in chart form:\n\nFrom these results we can infer that for this test Python 3.15 is just a tiny bit faster than 3.14, probably not enough to matter. As I have also observed in previous years, the performance of PyPy 3.12 is out of this world, clocking in at 5.5x the speed of 3.15, and even getting a small lead over Node.js. As for Rust there are no surprises, but it is always good to know where the limits are!\n\nComparing each Python release against the previous one shows an interesting detail. The only two releases that made significant performance improvements over their predecessors are Python 3.11 and 3.14. You can see this clearly in the chart, when there is a larger drop in the height of a bar compared to the previous one. The other releases either maintained the same speed or made small improvements, so overall there's always been progress.\n\nIn the next set of results you can see the progress of the JIT and free-threading (FT) releases of Python on the same test. Keep in mind that these alternative versions of the Python interpreter were first introduced in 3.13, so the range of versions to evaluate is much smaller.\n\n| fibo(40) - 1 thread | Time (secs) | vs. 3.15 | vs. previous | \n|---|---|---|---|\n| 3.13 JIT | 8.8339 |  |  | \n| 3.14 JIT | 7.1587 |  | 1.23x | \n| 3.15 JIT | 5.7625 | 1.20x | 1.24x | \n| 3.13 FT | 12.0368 |  |  | \n| 3.14 FT | 7.1185 |  | 1.69x | \n| 3.15 FT | 7.0298 | 0.99x | 1.01x | \n\nBelow is a chart with these results:\n\nReally the most interesting thing from this is that the JIT in the 3.15 interpreter was faster than the standard interpreter of the same test, which was not the case in previous years. And a 1.20x speed increase is not negligible, this is very exciting to see!\n\nOn the free-threading front there isn't really a lot to expect because this test is single-threaded. But we can say that the performance of the free-threading interpreter is about the same as the 3.14 one.\n\n## Test 2: bubble.py, single-threaded\n\nLet's look at the second test now. Here are the table and the chart for the single-threaded bubble sort test, which was configured to sort 10,000 random numbers:\n\n| bubble(10000) - 1 thread | Time (secs) | vs. 3.15 | vs. previous | \n|---|---|---|---|\n| 3.10 | 3.9918 | 0.49x |  | \n| 3.11 | 2.6237 | 0.75x | 1.52x | \n| 3.12 | 2.7341 | 0.72x | 0.96x | \n| 3.13 | 2.833 | 0.70x | 0.97x | \n| 3.14 | 2.0575 | 0.96x | 1.38x | \n| 3.15 | 1.9716 |  | 1.04x | \n| pypy3.12 | 0.1073 | 18.37x |  | \n| node-26.3 | 0.0643 | 30.66x |  | \n| rust-1.97 | 0.0391 | 50.42x |  | \n\nAs in the previous test, here we can also see that 3.15 had a very small improvement in performance with respect to 3.14. And we again can see that 3.11 and 3.14 are the two recent releases of Python that have really moved the needle in terms of performance. Some releases are even showing small regressions on this test. PyPy continued to be very fast, but for this test Node.js was faster.\n\nThe next table and chart show the results for the JIT and free-threading editions of the Python interpreter:\n\n| bubble(10000) - 1 thread | Time (secs) | vs. 3.15 | vs. previous | \n|---|---|---|---|\n| 3.13 JIT | 2.5887 |  |  | \n| 3.14 JIT | 2.3624 |  | 1.10x | \n| 3.15 JIT | 1.5435 | 1.28x | 1.53x | \n| 3.13 FT | 4.1888 |  |  | \n| 3.14 FT | 2.7248 |  | 1.54x | \n| 3.15 FT | 2.6808 | 0.74x | 1.02x | \n\nThis shows the same overall picture from the first test. The 3.15 JIT once again shows an impressive 1.28x speed gain over the regular interpreter. The free-threading version shows a performance drop with respect to the standard interpreter, but a similar drop occurred with the 3.14 interpreter, so this is not a regression. As I said before, this does not matter much because this test is single-threaded, so it isn't the kind of application that will ever help the free-threading interpreter shine. I think it is reasonable to expect the free-threading interpreter to perform comparably to the regular one, so from that point of view we can say that there is work to be done yet.\n\n## Test 3: fibo.py, multi-threaded\n\nLet's now repeat all the tests, but running 4 threads in parallel. Given that this is a very specific test that is designed to evaluate the free-threading version of the Python interpreter, I'm dropping the non-Python runs.\n\nTo get a baseline, first I ran the multi-threaded test on the standard Pythons. To be absolutely clear, these results are going to be bad for CPython, because the global interpreter lock (GIL) prevents true concurrency between threads. Here are the results and chart for the multi-threaded Fibonacci test:\n\n| fibo(40) - 4 threads | Time (secs) | vs. 3.15 | vs. previous | \n|---|---|---|---|\n| 3.10 | 67.2867 | 0.48x |  | \n| 3.11 | 49.726 | 0.65x | 1.35x | \n| 3.12 | 38.7307 | 0.84x | 1.28x | \n| 3.13 | 38.9421 | 0.84x | 0.99x | \n| 3.14 | 31.8367 | 1.02x | 1.22x | \n| 3.15 | 32.5421 |  | 0.98x | \n| pypy3.12 | 5.6385 | 5.77x |  | \n\nThis test shows 3.15 being a tiny bit slower than 3.14. We've seen in the single-threaded tests that the 3.15 interpreter was only slightly faster than 3.14, so overall I think we can say that the standard interpreter is about the same speed as 3.14. Here we can also see that PyPy continues to run circles around standard Python, but with the threads its speed slowed it down by a similar ratio, because PyPy's concurrency is also affected by a GIL.\n\nNow let's see how the JIT and free-threading versions of the Python interpreters do on this test.\n\n| fibo(40) - 4 threads | Time (secs) | vs. 3.15 | vs. previous | \n|---|---|---|---|\n| 3.13 JIT | 38.5661 |  |  | \n| 3.14 JIT | 30.9696 |  | 1.25x | \n| 3.15 JIT | 27.1641 | 1.20x | 1.14x | \n| 3.13 FT | 12.4376 |  |  | \n| 3.14 FT | 7.3052 |  | 1.70x | \n| 3.15 FT | 7.235 | 4.50x | 1.01x | \n\nThe free-threading edition of the Python 3.15 interpreter runs about 4.5 times faster than the standard interpreter, and the ratio was about the same with 3.14. This significant speed gain can be attributed to the interpreter running without the GIL, which allows for more efficient thread concurrency.\n\nA secondary observation that we can make from these results is that the JIT edition of the interpreter, which runs with the GIL, keeps a similar edge over the standard interpreter even when running multiple threads, and this is new with 3.15.\n\n## Test 4: bubble.py, multi-threaded\n\nWe have one more set of results to go over. Here are the standard interpreter results for the bubble sort test:\n\n| bubble(10000) - 4 threads | Time (secs) | vs. 3.15 | vs. previous | \n|---|---|---|---|\n| 3.10 | 16.5959 | 0.51x |  | \n| 3.11 | 10.7541 | 0.79x | 1.54x | \n| 3.12 | 10.979 | 0.77x | 0.98x | \n| 3.13 | 11.1337 | 0.76x | 0.99x | \n| 3.14 | 8.7198 | 0.97x | 1.28x | \n| 3.15 | 8.4514 |  | 1.03x | \n| pypy3.12 | 0.5165 | 16.36x |  | \n\nThis is, again, more or less in alignment with the previous results, with the 3.15 interpreter just a hair faster than 3.14.\n\nNow that we have the baseline for this test, let's have a look at the JIT and free-threading interpreters:\n\n| bubble(10000) - 4 threads | Time (secs) | vs. 3.15 | vs. previous | \n|---|---|---|---|\n| 3.13 JIT | 10.3004 |  |  | \n| 3.14 JIT | 9.9454 |  | 1.04x | \n| 3.15 JIT | 6.6417 | 1.27x | 1.50x | \n| 3.13 FT | 8.0547 |  |  | \n| 3.14 FT | 4.9934 |  | 1.61x | \n| 3.15 FT | 4.8549 | 1.74x | 1.03x | \n\nAnd here the free-threading 3.15 interpreter is once again faster than the standard one. The gains are not as impressive as in the Fibonacci test, but this type of program is still a good use case for a GIL-free interpreter.\n\nAs for the JIT results, they seem consistent with all other runs of this interpreter, which show a great improvement in 3.15.\n\n## Conclusions\n\nI hope you enjoyed looking at my benchmark. Maybe in addition to seeing a bunch of numbers and colorful charts, you are wondering what does this all mean in practical terms.\n\nWhat I want to do before ending this article is to give you my personal interpretation of what these numbers suggest, because you may want to know if it makes sense to upgrade to 3.15, and what improvements you can expect to see when you do. In this section we are leaving determinism and enter opinion territory, so please keep in mind that someone else looking at these numbers may have a completely different interpretation than mine!\n\nWith the disclaimer out of the way, I'll say that my gut feeling after running the tests is that Python 3.15 is at best a fairly minor improvement over 3.14 in terms of performance. I may eventually upgrade production projects I currently have on 3.14 such as this website, but the results that I obtained do not make me want to rush an upgrade like I did after seeing such great results with 3.14 last year.\n\nThe only area where there is a clear improvement in the 3.15 release is in the JIT. But the JIT continues to be an experimental feature, so it is not a good idea to use it in production. I will consider using the JIT in production only when it is out of the experimental phase.\n\nAside from the JIT, there aren't really any significant performance gains in this release. Some tests do show a small performance increment, but others show regressions, so I don't expect these small variations to translate into noticeable performance changes for a real world project. I honestly don't feel I'm losing anything by staying on 3.14 for a few more months, or even until 3.16 drops in a year and I have one more release to evaluate.\n\nI do, however, plan to use 3.15 as my main day-to-day interpreter, and maybe I will end up upgrading some of my production projects just so that I can use some of the new features, such as the JavaScript-like unpacking of comprehensions or the lazy imports.\n","body_html":"<p>It&#39;s October once again, and that means it is time to take the new release of Python for a spin (technically, it is the 3.15.0rc3 release that I&#39;m using, the official 3.15 release is still a few days out). As I did with my <a href=\"https://blog.miguelgrinberg.com/post/python-3-14-is-here-how-fast-is-it\" rel=\"nofollow ugc noopener\">Python 3.14 performance</a> article of a year ago, today I&#39;m sharing a new run of my informal Python benchmark, comparing Python 3.15 against previous interpreters all the way back to 3.10.</p>\n<p>If you are not interested in the charts and the tables and just want to read my analysis, feel free to jump to the <a href=\"https://blog.miguelgrinberg.com/post/how-fast-is-python-3-15#conclusions\" rel=\"nofollow ugc noopener\">conclusions</a> section at the end.</p>\n<h2 id=\"the-benchmark\">The benchmark</h2>\n<p>I just called my benchmark &quot;informal&quot;. What does that mean?</p>\n<p>Getting an objective and universal measure of the performance of a programming language is impossible. All you can do is write some programs and run them to get a measure of their performance. Other programs may show similar performance characteristics or they may not, there is really no way to know. My intention with this benchmark is just to get a feel for the performance changes across versions of Python, but I want to make it clear that I&#39;m not trying to obtain a comprehensive performance profile of the Python interpreter.</p>\n<p>For my benchmark I will be running two programs called <em>fibo.py</em> and <em>bubble.py</em>, which <a href=\"https://github.com/miguelgrinberg/pyspeed\" rel=\"nofollow ugc noopener\">you can inspect</a> if you like. These are the same programs I used in past editions of this benchmark. The first calculates numbers from the <a href=\"https://en.wikipedia.org/wiki/Fibonacci_sequence\" rel=\"nofollow ugc noopener\">Fibonacci sequence</a>, and the second sorts numbers using the <a href=\"https://en.wikipedia.org/wiki/Bubble_sort\" rel=\"nofollow ugc noopener\">bubble sort</a> algorithm.</p>\n<p>I&#39;ve chosen these two programs as representative of two classes of algorithms. The Fibonacci calculation is done using <a href=\"https://en.wikipedia.org/wiki/Recursion_(computer_science)\" rel=\"nofollow ugc noopener\">recursion</a>, which I have found to be somewhat inefficient in Python interpreters. On the other side, the bubble sort only uses for-loops, without any recursion. There are other types of programs that my benchmark does not attempt to cover. In particular, note that I&#39;m not including <a href=\"https://en.wikipedia.org/wiki/I/O_bound\" rel=\"nofollow ugc noopener\">I/O bound</a> code in this benchmark.</p>\n<p>Because some of the performance improvements in recent Python versions revolve around multi-threading, I also created a multi-threaded variation for each program, so in total I have four different tests.</p>\n<h2 id=\"the-testing-matrix\">The testing matrix</h2>\n<p>The complete testing matrix is actually fairly complex, because I have to run the four program variations under all the Python versions, plus the JIT and free-threading alternatives for those that have them. I like to run the tests under PyPy as well, because this interpreter has shown impressive performance in past runs of this benchmark. And to place Python performance within the wider ecosystem, I&#39;ve also ported the two programs to JavaScript (Node.js) and Rust.</p>\n<p>Here is the full test matrix that I&#39;ve worked with:</p>\n<ul><li>2 test scripts<ul><li><em>fibo.py</em> : calculates Fibonacci numbers, with recursion</li><li><em>bubble.py</em> : sorts a list of randomly generated numbers, without recursion</li></ul></li><li>2 threading modes<ul><li>Single-threaded</li><li>4 parallel threads</li></ul></li><li>6 Python versions, plus recent versions of PyPy, Node.js and Rust:</li><li>3 Python interpreters<ul><li>Standard</li><li>Just-In-Time (JIT): only for CPython 3.13+</li><li>Free-threading (FT): only for CPython 3.13+</li></ul></li></ul>\n<p>Readers of my previous benchmarks may recall that I had an additional dimension in my matrix for Linux vs. macOS. Given that there were no significant differences between them in the two previous runs of the benchmark, I&#39;ve decided to drop the macOS tests this time around, so all tests were executed on my Linux laptop, which has an Intel Core i5 CPU and runs Gentoo Linux.</p>\n<p>The method I&#39;m using to measure the performance of each participant in this benchmark is to run the test program three times and take the average duration of the three. In the tables of results that I share below I also show the speed difference versus the 3.15 version, and when it makes sense also the speed difference versus the previous version of a given interpreter. For speed comparisons I&#39;m using a simple ratio, where 1x means same speed, 0.5x means half speed (or that it took twice the time to run), 2x means twice as fast (or that it ran in half the time if you prefer), etc. Hopefully this makes sense.</p>\n<h2 id=\"test-1-fibo-py-single-threaded\">Test 1: fibo.py, single-threaded</h2>\n<p>Let&#39;s get started. The first test calculates the first 40 Fibonacci numbers.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>fibo(40) - 1 thread</th><th>Time (secs)</th><th>vs. 3.15</th><th>vs. previous</th></tr></thead><tbody><tr><td>3.10</td><td>15.9442</td><td>0.44x</td><td></td></tr><tr><td>3.11</td><td>9.4058</td><td>0.74x</td><td>1.70x</td></tr><tr><td>3.12</td><td>8.8545</td><td>0.78x</td><td>1.06x</td></tr><tr><td>3.13</td><td>8.8174</td><td>0.79x</td><td>1.00x</td></tr><tr><td>3.14</td><td>7.1659</td><td>0.97x</td><td>1.23x</td></tr><tr><td>3.15</td><td>6.9361</td><td></td><td>1.03x</td></tr><tr><td>pypy3.12</td><td>1.2517</td><td>5.54x</td><td></td></tr><tr><td>node-26.3</td><td>1.3899</td><td>4.99x</td><td></td></tr><tr><td>rust-1.97</td><td>0.0898</td><td>77.24x</td><td></td></tr></tbody></table></div>\n<p>Below you can see the above speeds in chart form:</p>\n<p>From these results we can infer that for this test Python 3.15 is just a tiny bit faster than 3.14, probably not enough to matter. As I have also observed in previous years, the performance of PyPy 3.12 is out of this world, clocking in at 5.5x the speed of 3.15, and even getting a small lead over Node.js. As for Rust there are no surprises, but it is always good to know where the limits are!</p>\n<p>Comparing each Python release against the previous one shows an interesting detail. The only two releases that made significant performance improvements over their predecessors are Python 3.11 and 3.14. You can see this clearly in the chart, when there is a larger drop in the height of a bar compared to the previous one. The other releases either maintained the same speed or made small improvements, so overall there&#39;s always been progress.</p>\n<p>In the next set of results you can see the progress of the JIT and free-threading (FT) releases of Python on the same test. Keep in mind that these alternative versions of the Python interpreter were first introduced in 3.13, so the range of versions to evaluate is much smaller.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>fibo(40) - 1 thread</th><th>Time (secs)</th><th>vs. 3.15</th><th>vs. previous</th></tr></thead><tbody><tr><td>3.13 JIT</td><td>8.8339</td><td></td><td></td></tr><tr><td>3.14 JIT</td><td>7.1587</td><td></td><td>1.23x</td></tr><tr><td>3.15 JIT</td><td>5.7625</td><td>1.20x</td><td>1.24x</td></tr><tr><td>3.13 FT</td><td>12.0368</td><td></td><td></td></tr><tr><td>3.14 FT</td><td>7.1185</td><td></td><td>1.69x</td></tr><tr><td>3.15 FT</td><td>7.0298</td><td>0.99x</td><td>1.01x</td></tr></tbody></table></div>\n<p>Below is a chart with these results:</p>\n<p>Really the most interesting thing from this is that the JIT in the 3.15 interpreter was faster than the standard interpreter of the same test, which was not the case in previous years. And a 1.20x speed increase is not negligible, this is very exciting to see!</p>\n<p>On the free-threading front there isn&#39;t really a lot to expect because this test is single-threaded. But we can say that the performance of the free-threading interpreter is about the same as the 3.14 one.</p>\n<h2 id=\"test-2-bubble-py-single-threaded\">Test 2: bubble.py, single-threaded</h2>\n<p>Let&#39;s look at the second test now. Here are the table and the chart for the single-threaded bubble sort test, which was configured to sort 10,000 random numbers:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>bubble(10000) - 1 thread</th><th>Time (secs)</th><th>vs. 3.15</th><th>vs. previous</th></tr></thead><tbody><tr><td>3.10</td><td>3.9918</td><td>0.49x</td><td></td></tr><tr><td>3.11</td><td>2.6237</td><td>0.75x</td><td>1.52x</td></tr><tr><td>3.12</td><td>2.7341</td><td>0.72x</td><td>0.96x</td></tr><tr><td>3.13</td><td>2.833</td><td>0.70x</td><td>0.97x</td></tr><tr><td>3.14</td><td>2.0575</td><td>0.96x</td><td>1.38x</td></tr><tr><td>3.15</td><td>1.9716</td><td></td><td>1.04x</td></tr><tr><td>pypy3.12</td><td>0.1073</td><td>18.37x</td><td></td></tr><tr><td>node-26.3</td><td>0.0643</td><td>30.66x</td><td></td></tr><tr><td>rust-1.97</td><td>0.0391</td><td>50.42x</td><td></td></tr></tbody></table></div>\n<p>As in the previous test, here we can also see that 3.15 had a very small improvement in performance with respect to 3.14. And we again can see that 3.11 and 3.14 are the two recent releases of Python that have really moved the needle in terms of performance. Some releases are even showing small regressions on this test. PyPy continued to be very fast, but for this test Node.js was faster.</p>\n<p>The next table and chart show the results for the JIT and free-threading editions of the Python interpreter:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>bubble(10000) - 1 thread</th><th>Time (secs)</th><th>vs. 3.15</th><th>vs. previous</th></tr></thead><tbody><tr><td>3.13 JIT</td><td>2.5887</td><td></td><td></td></tr><tr><td>3.14 JIT</td><td>2.3624</td><td></td><td>1.10x</td></tr><tr><td>3.15 JIT</td><td>1.5435</td><td>1.28x</td><td>1.53x</td></tr><tr><td>3.13 FT</td><td>4.1888</td><td></td><td></td></tr><tr><td>3.14 FT</td><td>2.7248</td><td></td><td>1.54x</td></tr><tr><td>3.15 FT</td><td>2.6808</td><td>0.74x</td><td>1.02x</td></tr></tbody></table></div>\n<p>This shows the same overall picture from the first test. The 3.15 JIT once again shows an impressive 1.28x speed gain over the regular interpreter. The free-threading version shows a performance drop with respect to the standard interpreter, but a similar drop occurred with the 3.14 interpreter, so this is not a regression. As I said before, this does not matter much because this test is single-threaded, so it isn&#39;t the kind of application that will ever help the free-threading interpreter shine. I think it is reasonable to expect the free-threading interpreter to perform comparably to the regular one, so from that point of view we can say that there is work to be done yet.</p>\n<h2 id=\"test-3-fibo-py-multi-threaded\">Test 3: fibo.py, multi-threaded</h2>\n<p>Let&#39;s now repeat all the tests, but running 4 threads in parallel. Given that this is a very specific test that is designed to evaluate the free-threading version of the Python interpreter, I&#39;m dropping the non-Python runs.</p>\n<p>To get a baseline, first I ran the multi-threaded test on the standard Pythons. To be absolutely clear, these results are going to be bad for CPython, because the global interpreter lock (GIL) prevents true concurrency between threads. Here are the results and chart for the multi-threaded Fibonacci test:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>fibo(40) - 4 threads</th><th>Time (secs)</th><th>vs. 3.15</th><th>vs. previous</th></tr></thead><tbody><tr><td>3.10</td><td>67.2867</td><td>0.48x</td><td></td></tr><tr><td>3.11</td><td>49.726</td><td>0.65x</td><td>1.35x</td></tr><tr><td>3.12</td><td>38.7307</td><td>0.84x</td><td>1.28x</td></tr><tr><td>3.13</td><td>38.9421</td><td>0.84x</td><td>0.99x</td></tr><tr><td>3.14</td><td>31.8367</td><td>1.02x</td><td>1.22x</td></tr><tr><td>3.15</td><td>32.5421</td><td></td><td>0.98x</td></tr><tr><td>pypy3.12</td><td>5.6385</td><td>5.77x</td><td></td></tr></tbody></table></div>\n<p>This test shows 3.15 being a tiny bit slower than 3.14. We&#39;ve seen in the single-threaded tests that the 3.15 interpreter was only slightly faster than 3.14, so overall I think we can say that the standard interpreter is about the same speed as 3.14. Here we can also see that PyPy continues to run circles around standard Python, but with the threads its speed slowed it down by a similar ratio, because PyPy&#39;s concurrency is also affected by a GIL.</p>\n<p>Now let&#39;s see how the JIT and free-threading versions of the Python interpreters do on this test.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>fibo(40) - 4 threads</th><th>Time (secs)</th><th>vs. 3.15</th><th>vs. previous</th></tr></thead><tbody><tr><td>3.13 JIT</td><td>38.5661</td><td></td><td></td></tr><tr><td>3.14 JIT</td><td>30.9696</td><td></td><td>1.25x</td></tr><tr><td>3.15 JIT</td><td>27.1641</td><td>1.20x</td><td>1.14x</td></tr><tr><td>3.13 FT</td><td>12.4376</td><td></td><td></td></tr><tr><td>3.14 FT</td><td>7.3052</td><td></td><td>1.70x</td></tr><tr><td>3.15 FT</td><td>7.235</td><td>4.50x</td><td>1.01x</td></tr></tbody></table></div>\n<p>The free-threading edition of the Python 3.15 interpreter runs about 4.5 times faster than the standard interpreter, and the ratio was about the same with 3.14. This significant speed gain can be attributed to the interpreter running without the GIL, which allows for more efficient thread concurrency.</p>\n<p>A secondary observation that we can make from these results is that the JIT edition of the interpreter, which runs with the GIL, keeps a similar edge over the standard interpreter even when running multiple threads, and this is new with 3.15.</p>\n<h2 id=\"test-4-bubble-py-multi-threaded\">Test 4: bubble.py, multi-threaded</h2>\n<p>We have one more set of results to go over. Here are the standard interpreter results for the bubble sort test:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>bubble(10000) - 4 threads</th><th>Time (secs)</th><th>vs. 3.15</th><th>vs. previous</th></tr></thead><tbody><tr><td>3.10</td><td>16.5959</td><td>0.51x</td><td></td></tr><tr><td>3.11</td><td>10.7541</td><td>0.79x</td><td>1.54x</td></tr><tr><td>3.12</td><td>10.979</td><td>0.77x</td><td>0.98x</td></tr><tr><td>3.13</td><td>11.1337</td><td>0.76x</td><td>0.99x</td></tr><tr><td>3.14</td><td>8.7198</td><td>0.97x</td><td>1.28x</td></tr><tr><td>3.15</td><td>8.4514</td><td></td><td>1.03x</td></tr><tr><td>pypy3.12</td><td>0.5165</td><td>16.36x</td><td></td></tr></tbody></table></div>\n<p>This is, again, more or less in alignment with the previous results, with the 3.15 interpreter just a hair faster than 3.14.</p>\n<p>Now that we have the baseline for this test, let&#39;s have a look at the JIT and free-threading interpreters:</p>\n<div class=\"table-wrap\"><table><thead><tr><th>bubble(10000) - 4 threads</th><th>Time (secs)</th><th>vs. 3.15</th><th>vs. previous</th></tr></thead><tbody><tr><td>3.13 JIT</td><td>10.3004</td><td></td><td></td></tr><tr><td>3.14 JIT</td><td>9.9454</td><td></td><td>1.04x</td></tr><tr><td>3.15 JIT</td><td>6.6417</td><td>1.27x</td><td>1.50x</td></tr><tr><td>3.13 FT</td><td>8.0547</td><td></td><td></td></tr><tr><td>3.14 FT</td><td>4.9934</td><td></td><td>1.61x</td></tr><tr><td>3.15 FT</td><td>4.8549</td><td>1.74x</td><td>1.03x</td></tr></tbody></table></div>\n<p>And here the free-threading 3.15 interpreter is once again faster than the standard one. The gains are not as impressive as in the Fibonacci test, but this type of program is still a good use case for a GIL-free interpreter.</p>\n<p>As for the JIT results, they seem consistent with all other runs of this interpreter, which show a great improvement in 3.15.</p>\n<h2 id=\"conclusions\">Conclusions</h2>\n<p>I hope you enjoyed looking at my benchmark. Maybe in addition to seeing a bunch of numbers and colorful charts, you are wondering what does this all mean in practical terms.</p>\n<p>What I want to do before ending this article is to give you my personal interpretation of what these numbers suggest, because you may want to know if it makes sense to upgrade to 3.15, and what improvements you can expect to see when you do. In this section we are leaving determinism and enter opinion territory, so please keep in mind that someone else looking at these numbers may have a completely different interpretation than mine!</p>\n<p>With the disclaimer out of the way, I&#39;ll say that my gut feeling after running the tests is that Python 3.15 is at best a fairly minor improvement over 3.14 in terms of performance. I may eventually upgrade production projects I currently have on 3.14 such as this website, but the results that I obtained do not make me want to rush an upgrade like I did after seeing such great results with 3.14 last year.</p>\n<p>The only area where there is a clear improvement in the 3.15 release is in the JIT. But the JIT continues to be an experimental feature, so it is not a good idea to use it in production. I will consider using the JIT in production only when it is out of the experimental phase.</p>\n<p>Aside from the JIT, there aren&#39;t really any significant performance gains in this release. Some tests do show a small performance increment, but others show regressions, so I don&#39;t expect these small variations to translate into noticeable performance changes for a real world project. I honestly don&#39;t feel I&#39;m losing anything by staying on 3.14 for a few more months, or even until 3.16 drops in a year and I have one more release to evaluate.</p>\n<p>I do, however, plan to use 3.15 as my main day-to-day interpreter, and maybe I will end up upgrading some of my production projects just so that I can use some of the new features, such as the JavaScript-like unpacking of comprehensions or the lazy imports.</p>","headings":[{"level":2,"text":"The benchmark","id":"the-benchmark"},{"level":2,"text":"The testing matrix","id":"the-testing-matrix"},{"level":2,"text":"Test 1: fibo.py, single-threaded","id":"test-1-fibo-py-single-threaded"},{"level":2,"text":"Test 2: bubble.py, single-threaded","id":"test-2-bubble-py-single-threaded"},{"level":2,"text":"Test 3: fibo.py, multi-threaded","id":"test-3-fibo-py-multi-threaded"},{"level":2,"text":"Test 4: bubble.py, multi-threaded","id":"test-4-bubble-py-multi-threaded"},{"level":2,"text":"Conclusions","id":"conclusions"}]}}