Speed Up Your Python Programs with Rust
When elegance meets blazing speed
Elegance.
This is the first word that springs to my mind when Python is mentioned.
However, no programming language is perfect. As an interpreted language, Python is much slower than compiled languages, such as C++ or Rust.
But how slow is it?
Talk is cheap. Let’s have a simple comparison between Python and Rust.
The following Python code measures the time required to compute the 30th Fibonacci number over 50 iterations:
import time
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
def main(test_times=50):
start = time.time()
for _ in range(test_times):
fib(30)
print(f"Total time spent: {time.time() - start} s")
main()
# Total time spent: 7.306154012680054 s
Over 7 seconds, it’s not ideal for most use cases.
“Blazingly fast”. This is how the official website of Rust describes its performance. Let’s compute the exact same Fibonacci number over 50 iterations in Rust and see what will happen:
use std::time;
fn fib(n: i32) -> u64 {
match n {
1 | 2 => 1,
_ => fib(n - 1) + fib(n - 2)
}
}
fn main() {
let test_times = 50;
let start = time::Instant::now();
for i in 0..test_times {
fib(30);
}
println!("Total time spent: {:?}", start.elapsed())
}
// Total time spent: 179.774166ms
179.774166 ms!
This is 40x faster than Python.
Should we give up Python now?
Of course not! The syntax of Rust, as far as I am concerned, is far from elegant, especially for Python developers.
It would be great if we could use Python as the main language of a large project but speed up some time-consuming parts with Rust.
Rewrite Slow Python Functions in Rust
The Python community has already done a lot of work on this idea. There are several approaches to rewrite low-performance Python functions in Rust.
One of the popular methods is using PyO3, an open-source tool about Rust bindings for the Python interpreter.
Now, it’s time to see how to make it work.
First and foremost, we need to install a module named maturin
:
pip install maturin
Then initialize the necessary files for Rust by this command:
maturin init
As the following screenshot shows, there are a few choices to do the Rust binding, we will choose PyO3 here.
This step will generate the needed files and folders for the Rust binding. All we need to do now is change two significant files:Cargo.toml
and lib.rs
.
The Cargo.toml
is a manifest for the package. It is written in the TOML format and contains metadata that is needed to compile the package.
In our case, we can simply change the relative names to rustFib
and keep other settings default.
[package]
name = "rustFib"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "rustFib"
crate-type = ["cdylib"]
[dependencies]
pyo3 = "0.19.0"
The lib.rs
file is where we should implement our function in Rust and use it to replace its time-consuming Python version later:
use pyo3::prelude::*;
/// The Python function implemented in Rust
#[pyfunction]
fn fib(n: i32) -> u64 {
match n {
1 | 2 => 1,
_ => fib(n - 1) + fib(n - 2)
}
}
/// Make it as a Python module.
#[pymodule]
fn rustFib(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(fib, m)?)?;
Ok(())
}
Finally, we merely need to execute one more command to compile the Rust code:
maturin develop
We successfully built a Python package with Rust named rustFib
. Will it be as fast as the original Rust program?
Let’s use it in Python right now:
import time
from rustFib import fib
def main(test_times=50):
start = time.time()
for _ in range(test_times):
fib(30)
print(f"Total time spent: {time.time() - start} s")
main()
# Total time spent: 0.17684102058410645 s
As the above code illustrates, the total time spent is 0.176 ms now. We really made the Python program as blazing fast as Rust!
Final Thoughts
The efforts to enhance the speed of Python programs are ceaseless. For some time-consuming tasks, leveraging compiled languages to rewrite them and use them as Python packages is a brilliant solution.
Thanks for reading ❤️, feel free to connect with me: