avatarYang Zhou

Summary

The article discusses the significant performance improvement achieved by integrating Rust code into Python applications to optimize time-consuming functions.

Abstract

The article "Speed Up Your Python Programs with Rust" highlights the performance gap between Python and Rust by comparing the execution time of a Fibonacci number calculation. Python takes over 7 seconds to compute the 30th Fibonacci number over 50 iterations, while Rust completes the same task in approximately 179.77 milliseconds, marking a 40x speed increase. To leverage Rust's performance within Python projects, the author suggests using PyO3, a Rust binding tool for Python. The article guides readers through setting up a Rust environment with maturin, modifying Cargo.toml and lib.rs files, and compiling Rust code to create a Python package. The result is a Python function that performs with the speed of Rust, demonstrated by a drastic reduction in execution time from seconds to milliseconds. The author concludes that integrating Rust into Python is a powerful strategy for enhancing performance in critical areas without abandoning Python's elegant syntax.

Opinions

  • The author believes that Python's syntax is elegant but acknowledges its performance limitations compared to compiled languages like Rust.
  • Rust is described as "blazingly fast," a testament to its efficiency and speed in computation-heavy tasks.
  • The article conveys that Python developers need not give up the language they love; instead, they can combine Python's ease of use with Rust's performance for optimal results.
  • The author implies that the Rust and Python integration process, while requiring some setup, is straightforward and accessible, thanks to tools like PyO3 and maturin.
  • The significant performance gain from using Rust within Python is emphasized as a compelling reason to adopt this approach for time-sensitive applications.

Speed Up Your Python Programs with Rust

When elegance meets blazing speed

Image from Wallhaven

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?

Image from Reddit

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:

X | Linkedin | Medium

Programming
Python
Rust
Technology
Rust Programming Language
Recommended from ReadMedium