Extending Python with Rust
Tags: performance, python3, rust, python
Published: 2019-10-26 17:00:21

---
title: Extending Python with Rust
published: true
date: 2019-05-01 17:37:44 UTC
tags: performance,python3,rust,python
canonical_url: 
---

![](https://cdn-images-1.medium.com/max/800/1*m9mLGeyjmV7Flq-t5MA62g.png)

### Introduction:

Python is a great programming language but sometimes it can be a bit of slowcoach when it comes to performing certain tasks. That’s why developers have been [building C/C++ extensions](https://docs.python.org/3/extending/building.html) and integrating them with Python to speed up the performance. However, writing these extensions is a bit difficult because these low-level languages are not type-safe, so doesn’t guarantee a defined behavior. This tends to introduce bugs with respect to memory management. Rust ensures memory safety and hence can easily prevent these kinds of bugs.

### Slow Python Scenario:

One of the many cases when Python is slow is building out large strings. In Python, the string object is immutable. Each time a string is assigned to a variable, a new object is created in memory to represent the new value. This contrasts with languages like Perl where a string variable can be modified in place. That’s why the common operation of constructing a long string out of several short segments is not very efficient in Python. Each time you append to the end of a string, the Python interpreter must allocate a new string object and copy the contents of both the existing string and the appended string into it. As the string under manipulation become large, this process can become increasingly slow.

Problem: Write a function which accepts a positive integer as argument and returns a string concatenating a series of integers from zero to that integer.

So let’s try solving the above problem in python and see if we can improve the performance by extending it via Rust.

### Python Implementations:

#### Method I: Naive appending

{% gist https://gist.github.com/chhetripradeep/fb1c2af57c7d3a65d9e2b0924defdcee %}

This is the most obvious approach. Using the concatenate operator (+=) to append each segment to the string.

#### Method II: Build a list of strings and then join them

{% gist https://gist.github.com/chhetripradeep/c2452b87b9df809a0c0ef5eec3222eb4 %}

This approach is commonly suggested as a very pythonic way to do string concatenation. First a list is built containing each of the component strings, then in a single join operation a string is constructed containing all of the list elements appended together.

#### Method III: List comprehensions

{% gist https://gist.github.com/chhetripradeep/4e1a604d225a75993802153485af8275 %}

This version is extremely compact and is also pretty understandable. Create a list of numbers using a list comprehension and then join them all together. This is just an abbreviated version of last approach and it consumes pretty much the same amount of memory.

Let’s measure the performance of each of these three approaches and see which one wins. We are going to do this using [pytest-benchmark](https://pypi.org/project/pytest-benchmark/) module.

{% gist https://gist.github.com/chhetripradeep/3b0cecfa5c7ead2a41bbf1d77e3d3e49 %}

Here is the result of the above benchmarks. Lower the value, better is the approach.

{% gist https://gist.github.com/chhetripradeep/e601bf2f4e06f625a8273d68d1124780 %}

Just by looking at the **Mean** column, one can easily justify that the list comprehension approach is definitely the winner among three approaches.

### Rust Implementations:

After trying out basic implementation of the above problem in Rust, and doing some rough benchmarking using [cargo-bench](https://github.com/rust-lang/cargo/blob/master/src/doc/man/cargo-bench.adoc), the result definitely looked promising. Hence, I decided to port the rust implementation as shared library using [rust-cpython](https://github.com/dgrunwald/rust-cpython) project and call it from python program.

To achieve this, I had create a rust crate with the following src/lib.rs.

{% gist https://gist.github.com/chhetripradeep/3c64187f83f598551b0bb05649553b91 %}

Building the above crate created a . **dylib** file which needs to be rename  **.so**.

{% gist https://gist.github.com/chhetripradeep/6a0c2d1749f5ddf39de889bea664a12e %}

Then, we ran the same benchmark including the rust one as before.

{% gist https://gist.github.com/chhetripradeep/0243d40048dfd3b27a96470981cb1e33 %}

This time the result is more interesting.

{% gist https://gist.github.com/chhetripradeep/08cad5cbd4ff501011b002d33165b954 %}

The rust extension is definitely the winner. As you increase the number of iterations to even more, the result is even more promising.

Eg. for iterations = 1000, following are the benchmark results

{% gist https://gist.github.com/chhetripradeep/b2962ea087ce39fc06896d61307b0345 %}

### Code:

You can find the code used in the post:

- [https://github.com/chhetripradeep/rust-python-example](https://github.com/chhetripradeep/rust-python-example)
- [https://github.com/chhetripradeep/cargo-bench-example](https://github.com/chhetripradeep/cargo-bench-example)

### Conclusion:

I am very new to Rust but these results definitely inspires me to learn Rust more. If you know better implementation of above problem in Rust, do let me know.

[PyO3](https://github.com/PyO3/pyo3) started a fork of [rust-cpython](https://github.com/dgrunwald/rust-cpython) but definitely has lot more active development and hence on my todo-list of experimentation.

Distributing of your python module will demand the rust extension to be compiled on the target system because of the variation of architecture. [Milksnake](https://github.com/getsentry/milksnake) is a extension of [python-setuptools](https://pypi.org/project/setuptools/) that allows you to distribute dynamic linked libraries in Python wheels in the most portable way imaginable.