How to Make Your Python Packages Really Fast with Rust

3 months ago 7

Goodbye, dilatory code

Isaac Harris-Holt

Towards Data Science

Photo by Chris Liverani connected Unsplash

Python is… slow. This is not a revelation. Lots of dynamic languages are. In fact, Python is truthful dilatory that galore authors of performance-critical Python packages person turned to different connection — C. But C is not fun, and C has capable ft guns to cripple a centipede.

Introducing Rust.

Rust is simply a memory-efficient connection with nary runtime oregon garbage collector. It’s incredibly fast, ace reliable, and has a truly large assemblage astir it. Oh, and it’s besides super easy to embed into your Python codification acknowledgment to fantabulous tools similar PyO3 and maturin.

Sound exciting? Great! Because I’m astir to amusement you however to make a Python bundle successful Rust step-by-step. And if you don’t cognize immoderate Rust, don’t interest — we’re not going to beryllium doing thing excessively crazy, truthful you should inactive beryllium capable to travel along. Are you ready? Let’s oxidise this snake.


Before we get started, you’re going to request to instal Rust connected your machine. You tin bash that by heading to and pursuing the instructions there. I would besides urge creating a virtual situation that you tin usage for investigating your Rust package.

Script overview

Here’s a publication that, fixed a fig n, volition cipher the nth Fibonacci fig 100 times and clip however agelong it takes to bash so.

This is simply a precise naive, wholly unoptimised function, and determination are plentifulness of ways to marque this faster utilizing Python alone, but I’m not going to beryllium going into those today. Instead, we’re going to instrumentality this codification and usage it to make a Python bundle successful Rust

Maturin setup

The archetypal measurement is to instal maturin, which is simply a physique strategy for gathering and publishing Rust crates arsenic Python packages. You tin bash that with pip instal maturin.

Next, make a directory for your package. I’ve called excavation fibbers. The last setup measurement is to tally maturin init from your caller directory. At this point, you’ll beryllium prompted to prime which Rust bindings to use. Select pyo3.

Image by author.

Now, if you instrumentality a look astatine your fibbers directory, you’ll spot a fewer files. Maturin has created immoderate config files for us, namely a Cargo.toml and pyproject.toml. The Cargo.toml record is configuration for Rust’s physique tool, cargo, and contains immoderate default metadata astir the package, immoderate physique options and a dependency for pyo3. The pyproject.toml record is reasonably standard, but it’s acceptable to usage maturin arsenic the physique backend.

Maturin volition besides make a GitHub Actions workflow for releasing your package. It’s a tiny thing, but makes beingness so much easier erstwhile you’re maintaining an unfastened root project. The record we mostly attraction about, however, is the record successful the src directory.

Here’s an overview of the resulting record structure.

├── .github/
│ └── workflows/
│ └── CI.yml
├── .gitignore
├── Cargo.toml
├── pyproject.toml
└── src/

Writing the Rust

Maturin has already created the scaffold of a Python module for america utilizing the PyO3 bindings we mentioned earlier.

The main parts of this codification are this sum_as_string function, which is marked with the pyfunction attribute, and the fibbers function, which represents our Python module. All the fibbers relation is truly doing is registering our sum_as_string relation with our fibbers module.

If we installed this now, we’d beryllium capable to telephone fibbers.sum_as_string() from Python, and it would each enactment arsenic expected.

However, what I’m going to bash archetypal is regenerate the sum_as_string relation with our fib function.

This has precisely the aforesaid implementation arsenic the Python we wrote earlier — it takes successful a affirmative unsigned integer n and returns the nth Fibonacci number. I’ve besides registered our caller relation with the fibbers module, truthful we’re bully to go!

Benchmarking our function

To instal our fibbers package, each we person to bash is tally maturin developin our terminal. This volition download and compile our Rust bundle and instal it into our virtual environment.

Image by author.

Now, backmost successful our file, we tin import fibbers, people retired the effect of fibbers.fib() and past adhd a timeit lawsuit for our Rust implementation.

If we tally this present for the 10th Fibonacci number, you tin spot that our Rust relation is astir 5 times faster than Python, contempt the information we’re utilizing an identical implementation!

Image by author.

If we tally for the 20th and 30th fib numbers, we tin spot that Rust gets up to being astir 15 times faster than Python.

20th fib fig results. Image by author.
30th fib fig results. Image by author.

But what if I told you that we’re not adjacent astatine maximum speed?

You see, by default, maturin developwill physique the dev mentation of your Rust crate, which volition forego galore optimisations to trim compile time, meaning the programme isn’t moving arsenic accelerated arsenic it could. If we caput backmost into our fibbers directory and tally maturin developagain, but this clip with the --release flag, we’ll get the optimised production-ready mentation of our binary.

If we present benchmark our 30th fib number, we spot that Rust present gives america a whopping 40 times velocity betterment implicit Python!

30th fib number, optimised. Image by author.

Rust limitations

However, we bash person a occupation with our Rust implementation. If we effort to get the 50th Fibonacci fig utilizing fibbers.fib(), you’ll spot that we really deed an overflow mistake and get a antithetic reply to Python.

Rust experiences integer overflow. Image by author.

This is because, dissimilar Python, Rust has fixed-size integers, and a 32-bit integer isn’t ample capable to clasp our 50th Fibonacci number.

We tin get astir this by changing the benignant successful our Rust relation from u32 to u64, but that volition usage much representation and mightiness not beryllium supported connected each machine. We could besides lick it by utilizing a crate similar num_bigint, but that’s extracurricular the scope of this article.

Another tiny regulation is that there’s immoderate overhead to utilizing the PyO3 bindings. You tin spot that present wherever I’m conscionable getting the 1st Fibonacci number, and Python is really faster than Rust acknowledgment to this overhead.

Python is faster for n=1. Image by author.

Things to remember

The numbers successful this nonfiction were not recorded connected a cleanable machine. The benchmarks were tally connected my idiosyncratic machine, and whitethorn not bespeak real-world problems. Please instrumentality attraction with micro-benchmarks similar this 1 successful general, arsenic they are often imperfect and emulate galore aspects of existent satellite programs.

I anticipation you’ve recovered this nonfiction helpful, and that it’s encouraged you to effort rewriting performance-critical parts of your Python codification successful Rust. Be wary that portion Rust tin velocity up a batch of things, it won’t supply overmuch vantage if astir of your CPU cycles are spent connected syscalls oregon web IO, arsenic those slowdowns are extracurricular the scope of your program.

If you’d similar to spot much implicit examples of Python packages written successful Rust, delight cheque retired the pursuing examples:

This nonfiction was based connected the publication for this video of mine, truthful if you’re much of a ocular learner, oregon would similar speedy notation material, consciousness escaped to cheque it out:

Or cheque retired the codification connected GitHub:

Stay fast!


Read Entire Article