LoginSignup
21
15

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-02

概要

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を作っていきます。
今回はシンプルで簡単です。

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

を実行し、テストコード

test.py
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側のクラスオブジェクトとしてパースするやり方に関しても書いていきたいと思います。

今回はこの辺で。

おわり。

21
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
15