Python 3.11 Performance
# 2022-10-13
# With the candidate release of Python 3.11, we measure Python's performance gains over the years.

Python 3.11 has had it's candidate release, and once again this next version of Python boasts significant performance improvements. Python is by no means a blazingly fast language, but faster runtimes are always appreciated, especially with the amount of Python running out there. In this post we investigate how much faster this new version of Python is, and see how it compares to older Python versions. We measure and compare the performance of each major Python version from 3.11 to 3.6.
Measuring Performance
We can use Docker to run code for each version of Python in a container, without having to manually install each version on our local machine. We can download and run the Docker base images for each Python version with a single bash command. For the release candidate of Python 3.11, we can run the command:
docker run -t -d python:3.11-rc-bullseye
The -d flag ensures Docker runs the container in the background. By the time you're reading this, a newer Python 3.11 version is likely to be available. For the list of Docker Python base images, visit the Docker website.
We can check this has worked by listing the Docker containers currently running.
docker container list
From this list, we can get the container ID of the Docker container for the python:3.11-rc-bullseye
Docker image. We can use this container ID to execute bash commands within this container using docker exec
.
We can check our commands are working by confirming the Python version within this container is as we expect. When running the command below, we should see some version of Python 3.11.
docker exec -it <CONTAINER_ID> python3 -V
pyperformance is a great Python module for benchmarking Python using a variety of tests, and we can use it to compare the speed of each version. We can install this package by executing a pip install command in our container.
docker exec -it <CONTAINER_ID> python3 -m pip install pyperformance
We can run the pyperformance module and specify a filename to send the outputs.
docker exec -it <CONTAINER_ID> pyperformance run -o py-311.json
The benchmarking will up to 20 minutes, but once its finished, we can view the raw outputs in the file created.
docker exec -it <CONTAINER_ID> cat py-311.json
We can now do the same for:
python:3.10.7-bullseye
python:3.9.14-bullseye
python:3.8.14-bullseye
python:3.7.14-bullseye
python:3.6.14-bullseye
Some of the Python images listed here will eventually slip out of date. For the most resent version of each Python base image, check the Docker website.
After following these steps, we will still have Docker containers running in the background, and the Docker images will be taking up space on our machine.
To stop the containers from running, we need the IDs for our containers. We can find these container IDs by running:
docker container list
Using each container ID, we can stop a Docker container from running with:
docker container stop <CONTAINER_ID>
Similarly, to remove the Docker images, we need to find the IDs for our Python base images using:
docker images
We can now remove each Docker image from our machine with:
docker rmi <IMAGE_ID>
Results
We can now try and make sense of the raw results files collected. We can use the pyperf Python module (installed with pip install pyperf
) to summarize the results. If we copy the contents of the JSON results files from each of the containers into a single directory on our machine, we can run the following command to directly compare the performance of any two Python versions.
pyperf compare_to py-310.json py-311.json --table
We can see that Python 3.11 is 1.20x faster than 3.10. This is a massive improvement and appears to be the largest performance boost two consecutive Python versions (at least since 3.6). Both Python 3.11 and 3.10 seem to be bring the largest performance improvements. Python has gone from incremental improvements in performance to suddenly changing gear and making huge leaps. Python has come a long way since the release of Python 3.6 in late 2016, with the latest version now almost 1.6x faster. Surprisingly, with our test we found no improvement in performance between Python 3.8 and 3.9, although improvements in speed was not a focus for Python 3.9. Your results may differ from the ones below. All tests were performed on the cheapest Linode Ubuntu server, with only a single core available, meaning these results may differ if multi-core performance was added into the mix.
Version | 3.11 | 3.10 | 3.9 | 3.8 | 3.7 |
---|---|---|---|---|---|
3.10 | 1.20x faster | - | - | - | - |
3.9 | 1.40x faster | 1.16x faster | - | - | - |
3.8 | 1.39x faster | 1.16x faster | 1.01x faster | - | - |
3.7 | 1.46x faster | 1.22x faster | 1.05x faster | 1.05x faster | - |
3.6 | 1.58x faster | 1.32x faster | 1.13x faster | 1.14x faster | 1.08x faster |

Back to Reality
Python is definitely speeding up over time, but it was never designed to be a fast language. Even Python 3.11 pales in comparison to languages like C, Go and Rust. A quick fibonacci benchmark can really highlight this fact.
Fibonacci Benchmark
Python
import time
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == '__main__':
start = time.time()
fibonacci(40)
finish = time.time()
print(f'Elapsed: {round(finish - start, 2)}s')
C
#include <stdio.h>
#include <time.h>
int fibonacci(int n) {
if (n < 2)
return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
clock_t start = clock();
fibonacci(40);
clock_t finish = clock();
printf("Elapsed: %.2fs", ((double)(finish - start))/CLOCKS_PER_SEC);
return 0;
}
Go
package main
import (
"fmt"
"time"
)
func fibonacci(n int64) int64 {
if n < 2 {
return n
}
return fibonacci(n - 1) + fibonacci(n - 2)
}
func main() {
start := time.Now()
fibonacci(40)
finish := time.Now()
fmt.Println("Elapsed: ", finish.Sub(start))
}
Rust
fn fibonacci(n: i64) -> i64 {
if n < 2 {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
fn main() {
use std::time::Instant;
let start = Instant::now();
fibonacci(40);
let elapsed = start.elapsed();
println!("Elapsed: {:.2?}", elapsed);
}