2023年7月: PythonとRustの融合:PyO3/maturinを使ったPythonバインディングの作成入門(kadowaki)

門脇@satoru_kadowakiです。 今月のPython Monthly Topicsでは、PythonとRustの融合を可能にする PyO3maturinについて紹介します。

はじめに

PythonRustはそれぞれ異なる特性を持つプログラミング言語です。 Pythonはシンプルな構文で初学者にも親しみやすく、データサイエンス、Web開発など高レイヤーのライブラリ群が充実しています。 しかし、パフォーマンスが要求される部分ではCやRustに比べて劣ることがあります。 [1]

一方、Rustはメモリやスレッドの安全性に重点を置いて設計されており、CPUやメモリなどの低レイヤーの処理効率に優れています。 プログラムを書くこと自体が難しいとされている低レイヤーの処理を、パフォーマンスを損なわず書くことができる言語として広く利用されるようになりました。

2つの言語の特徴を生かし、Pythonでパフォーマンスが要求される処理を全てRustで置き換えることができれば性能向上が期待できますが、それにはRustの学習コストもあり大変です。

全てのプログラムをRust化するのは難しくとも、場合によっては「ある特定の処理だけでもパフォーマンスを改善したい」ということもあるかと思います。 そこで本記事では、Pythonで書かれた簡単なプログラムの一部(関数)をRustで書き換え、maturinを使用してPythonバインディングとして呼び出す方法を紹介します。

Pythonにおけるその他の高速化方法

Pythonの高速化には、他にも以下のような手法が知られています。 それぞれに特徴がありますが、今回紹介するmaturinはRustを使用した比較的新しい手法です。 この機会にぜひ知っていただけたらと思います。

手法

概要

Cython

Pythonライクなコードを、C言語に変換することで高速化を行う言語

numba

PythonやNumPyで書かれた数値計算コードを高速に実行するためのJITコンパイラ

pypy

Pythonの実装の1つで RPython によるJITコンパイラによって高速化される。(一部のC拡張と互換性がないものもある)

PyO3とmaturinについて

本記事では、Rustで書かれたプログラムをPythonから呼び出して実行するために、PyO3とmaturinを使用します。 それぞれ以下のような役割があります。

PyO3はRustとPythonの相互運用を実現するためのライブラリです。 PyO3によってRustからPythonのオブジェクトや関数を呼び出す、またはその逆の操作も行えます。

maturinはPythonの拡張モジュールをビルドし、Rustの クレート(Crate) というパッケージ化を行うためのツールです。 内部でPyO3を使用しており、RustのコードをPythonから利用できる形にビルドするためのインターフェースを提供します。

それぞれの関係を簡単な図で表すと以下のようになります。

システム開発におけるmaturinとPyO3の関係

このように、maturinによってビルドが簡単になり、開発者は複雑なビルドプロセスを気にすることなくRustとPythonを使用した開発に集中することができます。 なお、PyO3とmaturinのGitHubリポジトリは、以下のようにどちらも PyO3配下 で開発が進められています。

以下にGitHubのリンクも掲載しておきます。

最近では、Pythonのデータ分析関連、バリデーションツール、暗号化ライブラリなど、様々なプロジェクトでPyO3やRustを使用している事例も増えてきており、PythonとRustがより親和性の高い言語になっていることが伺えます。

参考までにPyO3を使用しているプロジェクトには以下があり、Pythonのサードパーティライブラリの高速化にRustの特性が生かされています。

インストール

Rustのコードをビルドできるようにするために、Rustとmaturinをインストールします。 ビルドに必要な環境のセットアップは上記2つのみでPyO3自体のインストールは必要ありません。

rustupのインストール

まず最初に、Rustの開発環境をインストールします。 インストールは、Rust公式のインストーラー rustup を使用します。

本記事では以下の環境にインストールを行っています。

  • OS: Ubuntu 20.04.5 LTS

  • Python: 3.11.4

Linux、Unix系OS、macOSについては、Install Rust ページに記載されている手順で行います。 Windowsなど、その他のOSについては Other Rust Installation Methods ページでインストーラーが提供されていますので、確認してみてください。

rustupをインストールすると以下のような機能が提供されます。

  • Rustのバージョン管理: 特定のバージョンのインストールなどを行う

  • ツールチェーンの管理: コンパイルに必要なツールチェーンのインストールなどを行う

    • コンパイラー: rustc

    • パッケージマネージャー: Cargo

  • コンポーネントの管理: 標準ライブラリなどの管理を行う

rustupのインストールは以下のコマンドで行います。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

インストールを実行すると以下のようにインストール方法のオプション選択が表示されます。 標準(default)インストールである 1 を入力してEnterキーを押下します。

インストールオプションの選択画面
Welcome to Rust!

(省略)

Current installation options:

   default host triple: x86_64-unknown-linux-gnu
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1

インストールが続行され、以下のように必要なモジュール等のダウンロードとインストールが行われます。

インストール完了時の様子
info: profile set to 'default'
info: default host triple is x86_64-unknown-linux-gnu
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
info: latest update on 2023-06-01, rust version 1.70.0 (90c541806 2023-05-31)
info: downloading component 'cargo'
  6.9 MiB /   6.9 MiB (100 %)   2.6 MiB/s in  3s ETA:  0s

(省略)

Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).

To configure your current shell, run:
source "$HOME/.cargo/env"

インストールが完了すると、環境変数を再読み込みするために、シェルのリスタートまたは $HOME/.cargo/env ファイルの再読み込みが必要です。 標準出力の結果に記載があるとおり、以下のコマンドで行います。

$ source "$HOME/.cargo/env"

ここで念のために rustup コマンドが使用可能であることを確認します。 バージョン確認として -V オプションを設定しています。

$ rustup -V
rustup 1.26.0 (5af9b9484 2023-04-05)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.70.0 (90c541806 2023-05-31)`

maturinのインストール

続いてmaturinをpipコマンドでインストールします。

$ pip install maturin

以上でインストールは完了です。

Rust関数をPythonでバインディングする

ここからは、Pythonで作成した関数をRust化して実行する方法について説明します。

使用するPythonスクリプトのサンプル

以下のサンプルコードでは count_chars() 関数において、引数で指定された文字列について英字、数値、その他の文字列ごとにカウントした結果を返します。 このサンプルコード自体をPythonでさらに最適化する方法もあると思いますが、今回はこのスクリプトをRust化してみます。 なお、このサンプルスクリプトは後述の説明でも使用しますので、 example_base.py として保存しておきます。

example_base.py - 文字数カウントを行うPythonスクリプトのサンプル
# 文字列を引数として英字、数値、その他の文字列をカウントした結果を返す
def count_chars(text):
    # カウント用変数定義
    alphabet_count = 0  # 英字カウント用
    digit_count = 0  # 数値カウント用
    other_count = 0  # その他の文字カウント用

    for char in text:  # 文字列をループで繰り返し
        if char.isalpha() and char.isascii():  # アルファベットかどうか
            alphabet_count += 1
        elif char.isdigit():  # 数値かどうか
            digit_count += 1
        else:
            other_count += 1

    return alphabet_count, digit_count, other_count


if __name__ == "__main__":
    text = "Python Monthly Topics: 2023年7月"
    alphabet_count, digit_count, other_count = count_chars(text)
    print(f"アルファベットの数: {alphabet_count}")
    print(f"数字の数: {digit_count}")
    print(f"それ以外の文字数: {other_count}")

このサンプルスクリプトの実行結果は以下の通りです。

$ python example_base.py
アルファベットの数: 19
数字の数: 5
それ以外の文字数: 6

maturinで最初のステップ

最初にPython拡張モジュールにするためのディレクトリを作成します。 文字列カウントのスクリプトですので、ディレクトリ名を strcounter としました。 作成後、strcounter ディレクトリに移動します。

$ mkdir strcounter
$ cd strcounter

ディレクトリに移動後、以下のように maturin init コマンドを実行してRustのCargoプロジェクトを作成します。 (Cargoプロジェクトとは、Cargoによって管理されるRustのソフトウェアプロジェクトです。)

コマンドを実行するとバインディングの種類選択が要求されます。 先頭にある「pyo3」を選択した状態でEnterキーを押下します。

maturin initコマンドの実行結果
(strcounter)$ maturin init
? 🤷 Which kind of bindings to use?
  📖 Documentation: https://maturin.rs/bindings.html ›
❯ pyo3
  rust-cpython
  cffi
  uniffi
  bin

✔ 🤷 Which kind of bindings to use?
  📖 Documentation: https://maturin.rs/bindings.html · pyo3
   Done! Initialized project /home/ubuntu/..(省略)../strcounter

maturin init コマンドで表示されたバインディングの種類からもわかりますが、PyO3 以外のバインディングを選択することもできます。 その他のバインディングについては maturinのドキュメント - Bindings に説明がありますので、興味のある方は確認してみてください。

さて、 strcounter ディレクトリを見てみると、以下のようなファイルやディレクトリが作成されています。 (下記ではPythonで記載されたサンプルコードとの位置関係を分かりやすくするために、example_base.pyを含めて表示しています。)

$ tree -a
.
├── example_base.py  # pythonのサンプルスクリプト
├── strcounter
│   ├── .gitignore
│   ├── Cargo.toml
│   ├── pyproject.toml
│   ├── src
│      └── lib.rs
│   ├── .github
│      └── workflows
│           └── CI.yml

作成された主なファイルの概要は以下の通りです。

ファイル名

概要

Cargo.toml

Rustのビルドツールであるcargoの定義ファイル

pyproject.toml

Pythonパッケージのビルドに必要な情報の定義ファイル

src/lib.rs

Pythonバインディング用のscaffold(足場)

.github/workflows/CI.yml

GitHub Actions用ワークフロー定義ファイル

Cargo.tomlにはデフォルトのメタデータとPyO3の依存関係(バージョン)などが記載されています。 また、pyproject.tomlにはビルドツールとしてmaturinが使用されることなどがあらかじめ定義されています。

注目すべきはRustスクリプトを記述するsrc/lib.rsファイルです。 以下のようなscaffold(足場)が最初から記載されています。

src/lib.rsにデフォルトで記載されているscaffold
 1use pyo3::prelude::*;
 2
 3/// Formats the sum of two numbers as string.
 4#[pyfunction]
 5fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
 6    Ok((a + b).to_string())
 7}
 8
 9/// A Python module implemented in Rust.
10#[pymodule]
11fn strcounter(_py: Python, m: &PyModule) -> PyResult<()> {
12    m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
13    Ok(())
14}

Pythonであれば、関数は def キーワードが使用されますが、Rustでは fn キーワードが使用されます。 scaffoldの主要部分は、Python関数であることを表す #[pyfunction] 属性でマークされた sum_as_string() 関数(4-5行目)と、Pythonモジュールであることを表す #[pymodule] 属性でマークされた strcounter() 関数(10-11行目)です。 strcounter() 関数が行っていることは、sum_as_string() 関数をモジュールとして登録することだけです。

sum_as_sting() は文字列の連結を行う関数ですが、この部分を本記事のサンプルコード count_chars() 関数をRust化して置き換えるのが次のステップです。

関数をRust化して書き換える

先述のとおり、Rustを理解して書き進めていくにはそれなりの学習時間を必要とします。 詳細な解説を本記事で行っていくには限界があるため、本記事では最低限必要な部分の説明のみとしています。 とはいえ、本サンプルの関数自体は数行のコードで、Pythonのサンプルコードともほぼ同じような構成です。 Rustのコードを初めて見るという方もいると思いますが、とりあえず雰囲気で読んでみてください。(笑) maturinをきっかけにRustを少し勉強してみるきっかけになれば幸いです。(筆者もまだまだビギナーです!) [2]

Rust化にあたり、lib.rsファイルの変更点は以下の2点です。

  • sum_as_string() 関数を削除して count_chars() 関数を作成

  • 27行目の wrap_pyfunction!(sum_as_string, m)wrap_pyfunction!(count_chars, m) に変更

修正後のlib.rsファイルは以下のようになります。

lib.rs - Rust化されたcount_chars()関数
 1use pyo3::prelude::*;
 2
 3#[pyfunction]
 4fn count_chars(s: &str) -> (usize, usize, usize) {
 5    // カウント用変数定義
 6    let mut alphabet_count = 0;  // 英字カウント用
 7    let mut digit_count = 0;  // 数値カウント用
 8    let mut other_count = 0;  // その他の文字カウント用
 9
10    for c in s.chars() {  // 文字列をループで繰り返し
11        if c.is_ascii_alphabetic() {  // アルファベットかどうか
12            alphabet_count += 1;
13        } else if c.is_ascii_digit() {  // 数値かどうか
14            digit_count += 1;
15        } else {
16            other_count += 1;
17        }
18    }
19
20    (alphabet_count, digit_count, other_count)
21}
22
23
24/// A Python module implemented in Rust.
25#[pymodule]
26fn strcounter(_py: Python, m: &PyModule) -> PyResult<()> {
27    m.add_function(wrap_pyfunction!(count_chars, m)?)?;  // sum_as_stringをcount_charsに変更
28    Ok(())
29}

以上でPythonで書かれていたプログラムを、同じ処理を行うRust版のコードに書き換える作業は完了です。

作成したRustのコードをビルドする

作成した strcounter パッケージをビルドするには、コマンドで maturin develop を実行します。 ビルドを実行すると、Rustパッケージがダウンロード、コンパイルされ仮想環境(venv)にインストールされます。

maturin developコマンドによるビルドの様子
(strcounter)$ maturin develop
    Updating crates.io index
   (省略)
  Downloaded 6 crates (1.6 MB) in 0.80s
🔗 Found pyo3 bindings
   (省略)
   Compiling strcounter v0.1.0 (/home/ubuntu/..../strcounter)
    Finished dev [unoptimized + debuginfo] target(s) in 18.15s
📦 Built wheel for CPython 3.11 to /tmp/.tmpyBRxla/strcounter-0.1.0-cp311-cp311-linux_x86_64.whl
🛠 Installed strcounter-0.1.0

作成したスクリプトに問題がある場合には、コンパイルに失敗しエラーが表示されます。 コンパイルが問題なく完了すると、上記のように Installed.... のようなメッセージが表示され終了します。

ちなみに、成功した際のメッセージに Built wheel for CPython 3.11 to /tmp/.tmpyBRxla/strcounter-0.1.0-cp311-cp311-linux_x86_64.whl とあるように、Pythonのwheel形式(.whl)のバイナリが作成され、パッケージ化されていることがわかります。

ビルドしたRustパッケージをPythonバインディングとしてインポートする

それでは実際に使用してみましょう。 インポートは import strcounter とするだけです。 関数として呼び出すには strcounter.count_chars() のようにします。 特別な使用方法もなく、Pythonを普段から使用しているやり方と同じなのは親近感が湧きます。

example1.py - Pythonバインディングとしてインポートして使用するサンプルスクリプト
import strcounter


if __name__ == "__main__":
    text = "Python Monthly Topics: 2023年7月"
    alphabet_count, digit_count, other_count = strcounter.count_chars(text)  # Pythonバインディングを使用
    print(f"アルファベットの数: {alphabet_count}")
    print(f"数字の数: {digit_count}")
    print(f"それ以外の文字数: {other_count}")

実行結果は以下のとおりです。 結果を見ると正しく動作していることが確認できます。

$ python example1.py
アルファベットの数: 19
数字の数: 5
それ以外の文字数: 6

最適化ビルド

バインディングのビルドは maturin develop コマンドを使用して行いました。 このコマンドは、Rustクレートのdevバージョンをビルドするもので、コンパイル時間を短縮するために最適化はスキップされています。

開発したパッケージのパフォーマンスを最大化するには、最適化してビルドを行う必要があります。 最適化を行うには、strcounterディレクトリに戻って maturin develop --release と実行します。 --release フラグを付けることで、デバック情報や使用されていないデッドコードの除去などが行われ、最適化されたバイナリが作成されます。

ただし、最適化はビルド時間が長くなります。 そのため、開発中やデバッグ時には --release フラグを使わず、最終的なリリースの際に使用するのが一般的です。 フラグを付け忘れてしまうとパフォーマンスが出ません。 実際にリリースを行う際には、最適化を忘れないようにしましょう。

--releaseフラグを付けたビルドの様子
(strcounter)$ maturin develop --release
  (省略)
   Compiling strcounter v0.1.0 (/home/ubuntu/.../strcounter)
    Finished release [optimized] target(s) in 1m 35s
📦 Built wheel for CPython 3.11 to /tmp/.tmpHVs9Jg/strcounter-0.1.0-cp311-cp311-linux_x86_64.whl
🛠 Installed strcounter-0.1.0

標準出力に Finished release [optimized] target(s) in 1m 35s と表示されているように、ビルドに約1分半かかりました。 devバージョンのビルドは20秒弱で終了していたことと比較しても、最適化には時間がかかることがわかります。

パフォーマンス比較

最適化が完了したところで、実際にパフォーマンスがどれほど違うか見てみます。 example_base.pyを修正して、元々のPythonのコードとRust化されたPythonバインディングの両方の関数を実行するコードを作成します。 計測には time.timeit モジュールを使用し、100回実行した平均値を比較します。 また、計算に使用する文字列が少ないとそれほど差が出ないので、長めの文字列を使用してみます。今回は先月(2023年6月)のPython Monthly Topicsの記事 を使用してみることにしました。

計測用のコードでは、requestsモジュールを使用して上記記事URLのテキストを取得し計算します。 事前にrequestsモジュールを以下のコマンドでインストールします。

$ pip install requests
example2.py - 処理時間の計測をtimeitモジュールで行うサンプルスクリプト
import requests
import strcounter
from timeit import timeit


# 文字列を引数として英字、数値、その他の文字列をカウントした結果を返す
def count_chars(text):
    # カウント用変数定義
    alphabet_count = 0  # 英字カウント用
    digit_count = 0  # 数値カウント用
    other_count = 0  # その他の文字カウント用

    for char in text:  # 文字列をループで繰り返し
        if char.isalpha() and char.isascii():  # アルファベットかどうか
            alphabet_count += 1
        elif char.isdigit():  # 数値かどうか
            digit_count += 1
        else:
            other_count += 1

    return alphabet_count, digit_count, other_count


if __name__ == "__main__":
    # 特定のURLからテキストを抽出
    url = "https://gihyo.jp/article/2023/06/monthly-python-2306"
    res = requests.get(url)
    res.encoding = "utf-8"
    text = res.text

    loop = 100  # 繰り返し実行回数
    # 本スクリプト内のPython関数を実行結果を表示
    p_res = timeit(lambda: count_chars(text), number=loop)
    p_time = p_res / loop * 1_000_000  # 1回あたりの平均実行時間をマイクロ秒で計算
    print(f"Python Avg: {p_time:.2f} μs/call")

    # PythonバインディングによるRustの実行結果を表示
    r_res = timeit(lambda: strcounter.count_chars(text), number=loop)
    r_time = r_res / loop * 1_000_000  # 1回あたりの平均実行時間をマイクロ秒で計算
    print(f"Rust Avg: {r_time:.2f} μs/call")

なお、計測では実行結果を表示していませんが、どちらも同じ計算結果になることを確認しています。 参考までに出力結果は以下のとおりです。

アルファベットの数: 21188
数字の数: 4911
それ以外の文字数: 20102

計測の実行結果は以下のようになりました。 なんと驚きです!Pythonバインディングを経由した方が約40倍も速い結果となりました! たったこれだけの手間で数倍から数十倍の性能向上が期待できるなら、自分が書いたあんなロジックやこんなロジックもRustに任せることができるかもしれません!

$ python example2.py
Python Avg: 7723.11 μs/call
Rust Avg: 193.54 μs/call

「Python 🤝 Rust」は意外と簡単、それでも「銀の弾丸など無い」

maturinは想像以上にPythonへの取り回しがよく、しかも圧倒的なパフォーマンスを得られる可能性があることはとても魅力的です。

しかし、いくらRustが速いとは言えPyO3/maturinであらゆるロジックを置き換えられるというわけではありません。安易に使用したらパフォーマンスが得られなかった、ということもあります。

1つ例をあげてみます。以下のようなPythonのreモジュールを使用して、正規表現パターン、文字列、置換する文字列を引数として置換を行う関数を作成しました。 (サンプルスクリプトの実行結果は、 Hello-Python-Rust-with-maturin と表示されるだけです。)

regex_sub.py - reモジュールを使用した置換関数の例
import re


# 引数で指定された正規表現を使用して、テキストを置換する関数
def replace_with_pattern(pattern, text, replacement):
    return re.sub(pattern, replacement, text)


if __name__ == "__main__":
    pattern = r"\d+"  # 正規表現パターン
    text = "Hello01Python23Rust45with678maturin"  # 置換対象文字列
    replacement = "-"  # 置換する文字列

    # 本スクリプト内のPython関数を実行結果を表示
    print(replace_with_pattern(pattern, text, replacement))

これをmaturinで置き換えると以下のようなlib.rsで実現できます。 最初のサンプルと異なるのは、 replacer という名前で作成し、Rustで正規表現を使用するために regex クレートを指定しています。

lib.rs - regexクレートを使用した正規表現のサンプルスクリプト
use pyo3::prelude::*;
use regex::Regex;

// 引数で指定された正規表現を使用して、テキストを置換する関数
#[pyfunction]
fn replace_with_pattern(pattern: &str, text: &str, replacement: &str) -> String {
    let re = Regex::new(pattern).unwrap();
    re.replace_all(text, replacement).to_string()
}

/// A Python module implemented in Rust.
#[pymodule]
fn replacer(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(replace_with_pattern, m)?)?;
    Ok(())
}

以下は参考情報ですが、 regex クレートを使用する場合は、Cargo.tomlの [dependencies] 属性に以下のように依存関係を追記する必要があります。

[dependencies]
pyo3 = "0.19.0"
regex = "1"  # regexを追加

lib.rsファイルにRustのコードを記載したら maturin develop --release コマンドでビルドを行い、計測用のスクリプトを以下のように作成しました。 計測は前回と同じくtime.timeit モジュールで行っています。

regex_sub2.py - Pythonのreモジュールとの速度比較用スクリプト
import re
import replacer
from timeit import timeit


# 引数で指定された正規表現を使用して、テキストを置換する関数
def replace_with_pattern(pattern, text, replacement):
    return re.sub(pattern, replacement, text)


if __name__ == "__main__":
    pattern = r"\d+"  # 正規表現パターン
    text = "Hello01Python23Rust45with678maturin"  # 置換対象文字列
    replacement = "-"  # 置換する文字列

    loop = 100  # 繰り返し実行回数

    # 本スクリプト内のPython関数を実行
    p_res = timeit(lambda: replace_with_pattern(pattern, text, replacement), number=loop)
    p_time = p_res / loop * 1_000_000  # 1回あたりの平均実行時間をマイクロ秒で計算
    print(f"Python Avg: {p_time:.2f} μs/call")

    # Pythonバインディングによる実行
    r_res = timeit(lambda: replacer.replace_with_pattern(pattern, text, replacement), number=loop)
    r_time = r_res / loop * 1_000_000  # 1回あたりの平均実行時間をマイクロ秒で計算
    print(f"Rust Avg: {r_time:.2f} μs/call")

計測結果は以下のように、圧倒的にPythonの方が速い結果となりました。

$ python regex_sub2.py
Python Avg: 5.76 μs/call
Rust Avg: 822.12 μs/call

これはPythonバインディングを使用する際にオーバーヘッドがあるために起きています。 つまり、Pythonでもそれほど処理コストがかからない処理にRustを使用しても、Rustのメリットを受けられないということです。 他にも巨大なリストをPythonバインディング経由で処理することも試してみましたが、こちらはほぼ同じくらいの処理時間になり、それほどメリットがある結果にはなりませんでした。

このような結果から、maturinを使用する際に考慮するべきこととして、以下のことが言えます。

  • Python-Rust間のオーバーヘッド

    • Pythonで処理負荷が低いものをRust化してもメリットが出ないことがある

    • 比較的大きいオブジェクトのやりとりは、オーバーヘッドを考慮して実装する必要がある

  • CPUバウンドな処理が多い場合にはRustの恩恵を受けられる可能性が高い

  • IOバウンドな処理に採用してもメリットが出ないことがある

    • IOバウンドな処理+その後の処理負荷を考慮して検討する

まとめ

本記事では、maturinを使用してRustによるPythonバインディングの作成方法を紹介しました。 また、Rustに書き換える前のPythonコードと処理時間を計測して、高速化が行われていることを確認しました。 Rustはデータ分析など計算コストが高いプログラムを高速に処理することが得意です。 maturinを使用すれば、Pythonの使いやすさを維持したまま、Rustを取り入れたパフォーマンス向上を効率的に行うことができるようになります。

もちろん、わざわざRustを使用せずにPythonで高速化を行えるのが一番よいですが、maturinによってPythonとRustがより身近になり、パフォーマンスを向上させる手法が1つ増えたと言えます。

本記事がPythonのパフォーマンスで重要な処理を、Rustで書き換えてみるきっかけになれば幸いです。