Engineering / Benchmarks / CPython

Python 3.13, 3.14 and 3.15. Does newer really mean faster?

I wanted measurements, not promises: 14 workloads, three CPython versions, three separate sessions, all on one Intel Core i5-12600K. The outcome is far less predictable than a version number.

3
CPython versions
14
Workloads
3
Separate sessions
1,512
Benchmark executions

The takeaway: Python 3.15 delivers enormous gains in specific encodings and reproducible improvements in JSON, but it is not faster at everything. Some of the measured workloads still favor Python 3.13.

When comparing interpreters, it is easy to find one spectacular result, turn it into a headline, and ignore the rest. The trouble is that “Python is X% faster” means very little unless we know what ran, how it was measured, and how much uncertainty we can tolerate.

So I ran the same protocol with CPython 3.13.12, 3.14.3 and 3.15.0, installed using pyenv, on a Linux machine with an Intel Core i5-12600K. Each session comprised 12 rounds per workload and interpreter version, with 12 internal samples per execution. That's 504 executions per session and 18,144 internal samples overall.

First surprise: JSON really did get faster, consistently

Across all three sessions, Python 3.15 outperformed 3.14 when both encoding and decoding JSON. The consolidated advantage was 22.1% higher encoding throughput and 15.6% higher decoding throughput for these specific workloads. This is not a universal speedup for every API: it measures our standard-library benchmark with its particular dataset.

Where did Python 3.15 beat Python 3.14?

Relative speed difference · session-aggregated paired log-ratios · Base32/Base64 and startup without site excluded.

Bar length shows the magnitude of the difference, not an overall performance ranking. Gold means 3.15 won; gray means 3.14 won. Normal startup includes installation-specific environment effects and must be interpreted separately. Source: all three v3 sessions, with downloadable consolidated data below.

Base32 more than 200 times faster? Yes, with an important caveat.

The most striking result was Base32 running at roughly 219.6 times the speed of 3.14 in our workload. Base64 reached roughly 2.11 times the speed. Both effects reproduced in every session.

This does not mean Python 3.15 became 200 times faster overall. The optimization is specific: Python 3.15 introduces native implementations in binascii for some encodings that previously followed different code paths. The details are in the official Python 3.15 release notes.

Beware of averages: combining a more-than-200× result with 1–2% changes to proclaim “Python's average speedup” would produce an attention-grabbing number, but probably not one you could make an engineering decision with.

Second surprise: Python 3.13 still wins some workloads

The floating-point math-functions scenario favored 3.13 across all three sessions: 3.15 achieved approximately 17.6% lower execution throughput than 3.13 in that particular test. Regular expressions and comprehensions also favored the older version when compared with 3.15.

This does not justify declaring “Python 3.13 is better at math.” The scenario mixes particular operations, input sizes, and values; it does not represent NumPy, Pandas, or full scientific workloads. Precisely because the result is unexpected, it deserves independent confirmation with pyperformance or another purpose-built benchmark.

There was also good news for 3.14: asyncio ran around 23.8% faster than 3.13 in our synthetic task, and pure-Python ETL improved by about 9.5%. These are not direct forecasts of a production backend's performance.

All 14 workloads, including the small differences

The following table reports central execution times for each version and the consolidated 3.15-to-3.14 comparison. Lower times are better; a positive number in the final column means higher execution speed for 3.15.

Workload3.13.12 · ms3.14.3 · ms3.15.0 · ms3.15 vs. 3.14
Integers2.2672.1712.034+6.6%
Floating-point math1.3711.6991.664+1.1%
Comprehensions1.1151.3151.294+2.6%
Pure Python ETL0.9700.8780.898-1.5%
JSON · encoding0.3770.3820.312+22.1%
JSON · decoding0.4190.4250.371+15.6%
Regular expressions0.6590.8450.789+7.3%
In-memory SQLite1.2851.2991.289+0.9%
asyncio2.0351.6161.646-1.4%
I/O-bound threads6.5066.5556.180+1.4%
Base320.6390.7400.003≈219.6×
Base640.0050.0060.003+110.5%
Normal startup20.86610.72911.867-9.7%
Startup without site7.7117.6428.170-2.7%

Averaging these values without weighting workloads by their importance to a real application would be misleading. Central times and speed differences use different estimators (descriptive medians vs. paired log-ratios), so recomputing a percentage from rounded times can produce slightly different results.

Startup: a speedup that isn't quite what it seems

In the normal isolated-process test (-I), 3.14 started at almost twice the speed of 3.13. But when we disabled the site module using -S, the difference essentially vanished. That suggests the installation, module loading and environment contribute to what looks like a major interpreter-level win.

Startup deserves its own treatment because it measures something different from an integer loop or JSON operation. I would not cite that 2× figure as a promise that every command-line tool starts twice as fast.

How I designed the experiment

The goal was to change one main variable: the interpreter. Workloads used deterministic inputs and seeds, full-output SHA-256 validation outside the timed portion, warm-up, and counterbalanced interpreter ordering.

The statistical detail that matters

The largest improvements had comparable direction and magnitude across all three sessions. That is evidence of local reproducibility, not proof of a universal advantage. With just three genuinely separate sessions, the smallest possible two-sided exact-test p-value at session level is 0.25; after Holm correction, all 42 adjusted p-values are 1.0. We cannot claim statistical significance at the adjusted 5% level.

The 95% bootstrap intervals included in the dataset are exploratory: three sessions are not enough to make them guarantees about other computers. There was also variable activity on the sibling hyperthread, no exclusive core isolation, and builds that were not strictly identical.

What I am not concluding: “Python 3.15 is 22% faster overall,” “FastAPI will run 22% faster,” or “there is no longer a reason to use 3.13.” This experiment did not measure those claims.

Data, source code and reproduction

To make the numbers auditable, I am publishing the v3 benchmark script and the consolidated results used here. The JSON contains calculated comparisons and aggregated results for all three sessions; the CSV makes them easy to inspect in a spreadsheet or with Pandas. The three raw session captures are approximately 1 MB apiece and have been retained separately.

For reproduction, use the same settings: --rounds 12 --samples 12 --budget-ms 110. The site repository hosts the published source. The original raw files are preserved and can be released as an additional bundle; the consolidated JSON is not a complete replacement for them.

Which Python would I choose after this?

If my application spends a significant share of its time serializing JSON or performing Base32/Base64 encoding, 3.15 deserves a benchmark in my own environment. If performance hinges on numerical routines, regular expressions or native extensions, I would measure the actual application first—especially because interpreter internals and dependency compatibility can change across releases.

The conclusion is not that newer Python is bad. It's that the useful question was never “which Python wins?”, but “what changes for my application, by how much, and with what evidence?”

← Back to the engineering blog