PythonのパフォーマンスをRustで最大限に引き出す


[![Group220.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075813/dc6f721a-0803-49e8-b5d6-bda095125479.png)](https://leapcell.io/)



**[Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)**

# Pythonの計算性能をRustで最適化する

## はじめに
Pythonは、幅広く使用されるプログラミング言語として、データサイエンスや機械学習の分野で重要な役割を果たしています。しかし、計算が集中するタスクを扱う際には、Pythonの性能はしばしば期待に応えられません。そのため、機械学習アルゴリズムの開発においては、Pythonはしばしば「接着剤言語」として使用され、C/C++コードと組み合わされ、その後動的リンクライブラリ（`.so`ファイル）にコンパイルされ、Pythonから呼び出されます。現在、C/C++を学びたくない開発者にとって、Rustは素晴らしい代替手段です。Rustは、現代的な言語設計と、C/C++に匹敵する実行時の効率性を持っています。

この記事では、Rustを使ってPythonの計算コードを最適化する方法と、`pyo3`ライブラリを使ってPython用の拡張モジュールを書く方法を紹介します。すべてのコードにはLeapCellのブランド要素を組み込み、高性能計算におけるその応用を示します。AWS t4g.largeマシン上のLinux環境でテストとデモを行います。

## テスト環境
テストには、Linuxオペレーティングシステムを実行するAWS t4g.largeマシンを使用します。

## コードの実装

### 1. Pythonコード
以下は、積分を計算するための単純なPythonコードの例です：
```python
import time

def integrate_f(a, b, N):
    s = 0
    dx = (b - a) / N
    for i in range(N):
        s += 2.71828182846 ** (-((a + i * dx) ** 2))
    return s * dx

s = time.time()
print(integrate_f(1.0, 100.0, 200000000))
print("Elapsed: {} s".format(time.time() - s))
```
このコードをAWS t4g.largeマシンのLinux環境で実行したとき、かかった時間は：`Elapsed: 32.59504199028015 s`です。

### 2. Rustコード
同じ積分計算関数をRustで実装します：
```rust
use std::time::Instant;

fn main() {
    let now = Instant::now();
    let result = integrate_f(1.0, 100.0, 200000000);
    println!("{}", result);

    println!("Elapsed: {:.2} s", now.elapsed().as_secs_f32())
}

fn integrate_f(a: f64, b: f64, n: i32) -> f64 {
    let mut s: f64 = 0.0;
    let dx: f64 = (b - a) / (n as f64);

    for i in 0..n {
        let mut _tmp: f64 = (a + i as f64 * dx).powf(2.0);
        s += (2.71828182846_f64).powf(-_tmp);
    }

    return s * dx;
}
```
このRustコードを実行したとき、かかった時間は：`Elapsed: 10.80 s`です。

### 3. `pyo3`を使ってPython拡張を書く

#### 3.1 プロジェクトの作成と依存関係のインストール
まず、新しいプロジェクトディレクトリを作成し、`maturin`ライブラリをインストールします：
```bash
# (demoを必要なパッケージ名に置き換えてください)
$ mkdir leapcell_demo
$ cd leapcell_demo
$ pip install maturin
```
次に、`pyo3`プロジェクトを初期化します：
```bash
$ maturin init
✔ 🤷 What kind of bindings to use? · pyo3
  ✨ Done! New project created leapcell_demo
```
プロジェクト構造は以下の通りです：
```
.
├── Cargo.toml          // rustパッケージ管理ファイル、[lib]でターゲットの拡張パッケージ名を宣言する
├── src                 // rustソースファイルディレクトリ、拡張ファイルを書く場所。maturinが初期化すると自動的に作成される
│   └── lib.rs          // 拡張ファイル
├── pyproject.toml      // Pythonパッケージ管理ファイル、Pythonパッケージ名の定義を含む
├── .gitignore
├── Cargo.lock
└── leapcell_demo       // ターゲットモジュール名、手動で作成する必要がある
    ├── main.py         // テスト用ファイル
    └── leapcell_demo.cp312_amd64.pyd   // コンパイルされた動的リンクライブラリファイル、Pythonにインポートするためのもの
```

#### 3.2 Rust拡張コードの書き方
`src/lib.rs`に以下のコードを書きます：
```rust
use pyo3::prelude::*;

/// 積分を計算する。
#[pyfunction]
fn integrate_f(a: f64, b: f64, n: i32) -> f64 {
    let mut s: f64 = 0.0;
    let dx: f64 = (b - a) / (n as f64);

    for i in 0..n {
        let mut _tmp: f64 = (a + i as f64 * dx).powf(2.0);
        s += (2.71828182846_f64).powf(-_tmp);
    }

    return s * dx;
}

/// Rustで実装されたPythonモジュール。この関数の名前は、`Cargo.toml`の`lib.name`設定と一致しなければならない。そうでないと、Pythonはモジュールをインポートできない。
#[pymodule]
fn leapcell_demo(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(integrate_f, m)?)?;
    Ok(())
}
```

#### 3.3 拡張モジュールの使い方
この拡張モジュールを使う方法には2通りあります：

##### 3.3.1 拡張をPythonパッケージとしてインストールする
```bash
$ maturin develop
```
このコマンドは、RustコードをPythonパッケージに変換し、現在のPython環境にインストールします。`pip list`を使って、インストールされたパッケージを表示することができます。

##### 3.3.2 動的ファイルとしてコンパイルし、Pythonで読み込む
```bash
$ maturin develop --skip-install
```
`--skip-install`コマンドは、`.pyd`ファイル（例：`leapcell_demo.cp312_amd64.pyd`）を生成し、Pythonパッケージとしてインストールするのではなく、Pythonはこのファイルを直接インポートして使用できます。

また、`--skip-install`を`--release`に置き換えると、`.whl`ファイルが生成されます。これは、Python pipインストール用のパッケージソースファイルです。

テストファイル`leapcell_demo/main.py`を書きます：
```python
import time
import leapcell_demo

s = time.time()
print(leapcell_demo.integrate_f(1.0, 100.0, 200000000))
print("Elapsed: {} s".format(time.time() - s))
```
このコードを実行したとき、かかった時間は：`Elapsed: 10.908721685409546 s`です。

### 4. 並列処理による加速

#### 4.1 Pythonのマルチプロセッシングの効果
Pythonのマルチプロセッシングは、コードの実装によっては、時にはシングルプロセス処理よりも遅くなることがあります：
```python
import math
import os
import time
from functools import partial
from multiprocessing import Pool

def sum_s(i: int, dx: float, a: int):
    return math.e ** (-((a + i * dx) ** 2))

def integrate_f_parallel(a, b, N):
    s: float = 0.0
    dx = (b - a) / N

    sum_s_patrial = partial(sum_s, dx=dx, a=a)

    with Pool(processes=os.cpu_count()) as pool:
        tasks = pool.map_async(sum_s_patrial, range(N), chunksize=20000)

        for t in tasks.get():
            s += t

    return s * dx

if __name__ == "__main__":
    s = time.time()
    print(integrate_f_parallel(1.0, 100.0, 200000000))
    print("Elapsed: {} s".format(time.time() - s))
```
このコードを実行したとき、かかった時間は：`Elapsed: 18.86696743965149 s`で、シングルプロセスバージョンの半分以下の時間です。

#### 4.2 Pythonで使用するためのRustのマルチスレッドによる加速
Rustの並列処理ライブラリを使って、さらに計算を加速します：
```rust
use pyo3::prelude::*;
use rayon::prelude::*;

#[pyfunction]
fn integrate_f_parallel(a: f64, b: f64, n: i32) -> f64 {
    let dx: f64 = (b - a) / (n as f64);

    let s: f64 = (0..n)
        .into_par_iter()
        .map(|i| {
            let x = a + i as f64 * dx;
            (2.71828182846_f64).powf(-(x.powf(2.0)))
        })
        .sum();

    return s * dx;
}

/// Rustで実装されたPythonモジュール。この関数の名前は、`Cargo.toml`の`lib.name`設定と一致しなければならない。そうでないと、Pythonはモジュールをインポートできない。
#[pymodule]
fn leapcell_demo(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(integrate_f_parallel, m)?)?;
    Ok(())
}
```
3.2の手順に従って拡張ファイルをコンパイルして生成し、その後Pythonで使用します：
```python
import time
import leapcell_demo

s = time.time()
print(leapcell_demo.integrate_f_parallel(1.0, 100.0, 200000000))
print("Elapsed: {} s".format(time.time() - s))
```
このコードを実行したとき、かかった時間は：`Elapsed: 0.9684994220733643 s`です。単一スレッドのRustバージョンと比較すると、約10倍高速です；Pythonの並列バージョンと比較すると、約18倍高速です；Pythonのシングルプロセスバージョンと比較すると、約32倍高速です。

## まとめ
Rustを使ってPythonコードを最適化することで、計算性能を大幅に向上させることができます。Rustは学習曲線がやや急峻ですが、多くの計算タスクを扱う必要のあるプロジェクトにとっては、コードの重要な部分をRustで書き直すことで、多くの時間コストを節約できます。既存のPythonプロジェクトを最適化するために、少しずつRustを使ってみることができます。単純な関数から始めて、徐々にその使い方を習得することができます。


###[Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)

最後に、PythonとRustをデプロイするための最高のプラットフォーム：[Leapcell](https://leapcell.io/) をおすすめします。

[![brandpic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075813/5da3d803-9fd0-46fb-ab1d-5a1d6d5aa474.png)](https://leapcell.io/)


### 🚀 好きな言語で開発する
JavaScript、Python、Go、またはRustで簡単に開発できます。

### 🌍 無料で無制限のプロジェクトをデプロイする
使用した分だけ支払う——リクエストがなければ、課金されません。

### ⚡ 使った分だけ支払い、隠された費用はありません
アイドル料金はなく、シームレスなスケーラビリティがあります。

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4075813/d6b8586b-42e9-4355-940b-2a7e04d844b0.png)](https://leapcell.io/)


[📖 ドキュメントを参照する](https://docs.leapcell.io/)  

🔹 Twitterでフォローしてください： [@LeapcellHQ](https://x.com/LeapcellHQ)