概要
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(())
となっていて、このモジュールが関数として定義され、
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")
を実行します。
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側のクラスオブジェクトとしてパースするやり方に関しても書いていきたいと思います。
今回はこの辺で。
おわり。