乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

高速化

今回はPythonからRustを呼び出して
超高速化する方法についてです。

解説動画はこちら




Pythonは書きやすくて便利だけど
他の言語に比べるとどうしても遅い。


そんな時はRustの高速なネイティブコードを
Pythonから呼び出して計算速度を上げられます。


Python-Rust連携の方法

maturin

Rustで書かれたコードをPythonのパッケージ(wheel形式)
としてビルド・公開するためのビルドツールです。

これを使ってPythonとRustを連携させます。

主な手順は以下です。
1.Rust と maturin をインストール
2.Rust拡張用のプロジェクトを作る
3.Rustコードを作る
4.Cargo.toml を修正する
5.Python用拡張としてビルドする

早速手順を見ていきましょう。

なおGoogle ColabでのRust-Python連携方法になります。
自前のPCだとかだと、少し手順は変わってきます。

1.Rust と maturin をインストール

まず初めはColab内に必要なものをインストールします
# Rust をインストール
!curl https://sh.rustup.rs -sSf | sh -s -- -y -q
import os
os.environ["PATH"] = f"{os.environ['HOME']}/.cargo/bin:" + os.environ["PATH"]
!rustc --version
!cargo --version
!pip install -U maturin
!maturin --version

2. Rust拡張用のプロジェクトを作る
デフォルトのディレクトリはcontentになっているので
その中にプロジェクトを作り移動します。
!maturin init fastcalc
%cd /content/fastcalc

UIのフォルダマークからディレクトリが
作成されていると思います。


3. Rustコードを作る

すでにあるコードを上書きする形で
コードを書き換えます。
ここでは簡単な計算を行うコードを指定して
関数を作っています。

%%writefile src/lib.rs
use pyo3::prelude::*;

#[pyfunction]
fn sum_squares(n: u64) -> u64 {
    let mut s = 0;
    for i in 0..n {
        s += i * i;
    }
    s
}

#[pymodule]
fn fastcalc(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(sum_squares, m)?)?;
    Ok(())
}

4. Cargo.toml を修正

設定用のファイルも上書きします。

%%writefile Cargo.toml
[package]
name = "fastcalc"
version = "0.1.0"
edition = "2021"

[lib]
name = "fastcalc"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.21", features = ["extension-module"] }

5. Python用拡張としてビルド

RustコードをPythonライブラリとしてビルドします。

# Python環境から Rust を見えるように PATH を追加
!export PATH="$HOME/.cargo/bin:$PATH"

# wheel を作る
!maturin build --release

# wheel を pip install
!pip install target/wheels/*.whl

6.Pythonから Rust 関数を呼ぶ

1-5までが出来ていたら
ライブラリを呼び出して、Rustの関数が実行できます。

import fastcalc
fastcalc.sum_squares(100_000_000)

7.Python版と速度比較

これで速度比較が出せると思います。

import time

def sum_squares_py(n):
    s = 0
    for i in range(n):
        s += i * i
    return s

N = 100_000_000

t0 = time.time()
sum_squares_py(N)
t1 = time.time()
python_time = t1 - t0

t2 = time.time()
fastcalc.sum_squares(N)
t3 = time.time()

rust_time = t3 - t2
print("Python: {:.8f} sec".format(python_time))
print("Rust  : {:.8f} sec".format(rust_time))
print("Rust vs Python : {:.10f}".format(python_time / rust_time))

どれくらいの速度差になるかは
是非動画の方を見てみてください。

動画内ではもう一つの
シミュレーションも行っていますので
参考になると思います。



まとめ

maturinを使ったRustコードのPython拡張を作って実行できるようにすると
実行速度が高速な関数を呼び出せるようになります。


Pythonの簡単さはそのままに、重い計算はRustに任せる
これが Python × Rust 連携の最強パターンです。


最近はPolarsなどの大量データ処理や
深層学習モデルの構築や推論なども
Rust製ライブラリが増えてきています。

無いものは自分で作れると
自作の処理の時短になって
めちゃくちゃ捗ります。

今日はここまでです
それでは。

今回はPythonの実装を
めちゃくちゃ高速化出来るという
codonライブラリを試してみました


解説動画はこちら




このcodonライブラリですが
C/C++に匹敵する実行速度を実現出来るという
コンパイラです

Pythonのライブラリだと他にも

PyPy
Cpython
Numba 

などのコンパイル型
高速化ライブラリが存在しますが
それを超える速度を出せるとか・・・

一応の制約として
標準ライブラリ以外は
含める事ができない制約があるようです


高速化のベンチマークなどは
こちらにコードがあります

codon

今回はGoogle Colabで
検証してみたいと思います


Google Colab上へのインストール
%%bash
/bin/bash -c "$(curl -fsSL https://exaloop.io/install.sh)"

まずはライブラリをインストールしないと
動かないのでこれでコマンド等をインストールします

インストール後はcodonコマンドが使えます
実行の仕方
1.Pythonの実装ファイルを作成する「.py」
2.Google Colabのファイル置き場に置く
3.codonコマンドでコンパイルしながら実行
codon run ファイル名

これでコンパイルしながら実行されます

4.実行用のファイルをコンパイルする
codon build -release -exe ファイル名
コンパイルしたファイルの実行
./ファイル名

これらでPython実装からcodonで
実行が行えます




素数判定のプログラムで検証

まずは素数の判定に使われるプログラムで
時間を計測してみます

prime.pyというファイルにして
ファイルを配置します

# Pythonでの実装
from sys import argv
from time import time
def is_prime(n):
    factors = 0
    for i in range(2, n):
        if n % i == 0:
            factors += 1
    return factors == 0
limit = int(10000)
total = 0
t0 = time()
for i in range(2, limit):
    if is_prime(i):
        total += 1
t1 = time()
print(total)
print(t1 - t0)
1229
2.6382787227630615

Pythonでの実装は2.63秒ほどでした

codonでコンパイルしながら実行します
!/root/.codon/bin/codon run /content/prime.py
1229
0.112064

20倍ほどは早くなっていますね





アッカーマン関数での比較

次はアッカーマン関数で比較です
この関数は非負整数 m と n で
定義される関数のことで

与える数が大きくなると
爆発的に計算量が大きくなり
性能測定などに用いられるようです

このプログラムを使用しました
再帰を伴うので、上限を開放しておかないと
動かない様です
# pythonによる実装
import time
import sys

#再帰回数の上限を開放
sys.setrecursionlimit(100000)

def ackermann(m: int, n: int) -> int:
    if m == 0:
        return n + 1
    elif n == 0:
        return ackermann(m - 1, 1)
    else:
        return ackermann(m - 1, ackermann(m, n - 1))

m, n = 3, 10
start = time.time()
result = ackermann(m, n)
end = time.time()
print(f"ackermann({m}, {n}) = {result}")
print(f"elapsed time: {int((end - start) * 1000)} [ms]")
ackermann(3, 10) = 8189
elapsed time: 7522 [ms]


codon版は再帰の回数を少し増やしています
!/root/.codon/bin/codon run /content/coden.py
ackermann(3, 11) = 16381
elapsed time: 847 [ms]


最適化してコンパイルしておいたものを
実行してみると

!/root/.codon/bin/codon build -release -exe /content/coden.py
!/content/coden
ackermann(3, 11) = 16381
elapsed time: 185 [ms]

同じ回数ではないですが
再帰回数が2倍に増えていても
実行時間は50倍ほどは速そうです


ベンチマークによると
100倍ほど早くなるそうで
簡単なアルゴリズムなんだけど
実行回数が多いとか
時間がかかる部分を大幅に短縮出来ますね

pure python が輝ける日が来るかもしれません

今回はPython実装を高速化させる
codon について紹介しました

今回はここまでです
それでは


このページのトップヘ