概要

1. Pyhtonでアルゴリズムまで書いてあるのは速度面では好ましくないな〜
2. よし、C, C++あたりで書いてあるものを探して、それをPythonから呼んで高速化しよう。
3. なかなかいいライブラリ見つからんな、
4. おっ、Rustていう言語で書かれてるのならあったぞ
5. RustてPythonから呼べんのか??

これは、PythonからRustを呼んで高速化! PyO3 チュートリアル:簡単な関数をラップする その➀

の続きになります。

今回の目標

今回は、
– lib.rsを追記
– setup.py を加え実際にコンパイル

していきます。

最終的な目標は、
Rustで書いた関数やクラス(的なもの?)をPythonから気軽に呼べるようになること
です。

Rustの関数をPythonへ引き渡す


//lib.rsの続き
use pyo3::{wrap_pyfunction};

// ======================CREATING MODULE FOR PYTHON==================================================
/// This module is a python module implemented in Rust.
#[pymodule]
fn test_library(py: Python, m: &PyModule) -> PyResult<()> {
    m.add_wrapped(wrap_pyfunction!(get_prime_numbers))?;

    Ok(())
}

ここでは、
#[pymodule]のデコレータがtest_libraryがPythonのモジュールになることを宣言しています。

そして、先ほどのget_prime_numbers関数が、


m.add_wrapped(wrap_pyfunction!(get_prime_numbers))?;

でそのモジュールに関数としてラップされています。

最後に Ok(())となっていて、このモジュールが関数として定義され、
rust
PyResult<()>

空のPyResultを返すようになっている(Void関数はこれでラップできる)のが少し気になりますが、
この辺はおまじないとしてチュートリアルをそのまま書きました。

setup.py を作る

Cythonでも使用したおなじみのsetup.pyを作っていきます。
今回はシンプルで簡単です。

from setuptools import setup
from setuptools_rust import Binding, RustExtension

setup(name='test_library',
        version='0.1',
        rust_extensions=[
            RustExtension('test_library', 'Cargo.toml',
                binding=Binding.PyO3)],
            zip_safe=False)
name='test_library',

によりPython側からimport test_libraryのように呼べます。

RustExtension('test_library', 'Cargo.toml',
                binding=Binding.PyO3)],

でlib.rsの中のtest_library モジュールをpyo3によってライブラリ化します。
この時、使う依存関係がCargo.tomlと書くだけなのがとても簡単ですね。。

ビルドしてみる

以上で準備ができたので、実際にビルドしPythonから関数を呼んでみます。


python setup.py install

を実行し、テストコード

import test_library 
import time
import sys

def get_prime_numbers(n: int):
    flags = [True for _ in range(n+2)]

    upper = int(n ** 0.5)
    for i in range(2, upper+1):
        if not flags[i]:
            continue

        prime = i

        j = prime * 2
        while j <= n:
            flags[j] = False
            j += prime

    primes = []
    for i in range(2, n+1):
        if flags[i]:
            primes.append(i)

    return primes

if __name__  == "__main__":
    # just calling rust function from created library
    a = 123
    b = 456
    c = test_library.sum_as_string(a, b)

    print(c)



    # just calling rust function from created library, to find primes
    # rust is (of course) a lot faster than python
    # if you wanna call python function, python test.py, 
    # if you wanna call rust   function, python test.py --rust

    use_rust = len(sys.argv) == 2 and sys.argv[1] == "--rust"
    n = 10000


    t1 = time.time()
    for _ in range(10):
        if use_rust:
            primes = test_library.get_prime_numbers(n)
        else:
            primes = get_prime_numbers(n)

    t2 = time.time()

    print(f"time took is: {t2-t1} sec")

RustモジュールをPythonから実行する (PyO3)から引用

を実行します。

python test.py

でpythonベースの関数が走り、

python test.py --rust

でRustベースの関数を呼んでいます。

結果、


$ python test.py
time took is: 0.013466835021972656 sec

$ python test.py --rust
time took is: 0.0005574226379394531 sec

となり、実行速度差が顕著に現れてくれました。

まとめ

最終的な目標は、
Rustで書いた関数やクラス(的なもの?)をPythonから気軽に呼べるようになること
でしたが、
今回でRustで書かれた関数をPythonから呼ぶ方法を解説しました。
まだカバーした型変換がVec -> List だけなので、次の記事でまた他の型に関しても書ければと思います。ただ、あまり記述は変わらないので、簡単です。

また、Rustのクラス的なもの(Struct + method)をPython側のクラスオブジェクトとしてパースするやり方に関しても書いていきたいと思います。

今回はこの辺で。

おわり。

广告
将在 10 秒后关闭
bannerAds