概要
1. Pyhtonでアルゴリズムまで書いてあるのは速度面では好ましくないな〜
2. よし、C, C++あたりで書いてあるものを探して、それをPythonから呼んで高速化しよう。
3. なかなかいいライブラリ見つからんな、
4. おっ、Rustていう言語で書かれてるのならあったぞ
5. RustてPythonから呼べんのか??
最終的な目標
というわけで、今までC++のライブラリをPythonから呼んで高速化を図るための「Cython」チュートリアルを書いてきましたが、
今回からはRustをPythonから呼んで高速化するための「PyO3」というライブラリのチュートリアルを書いていきます。
最終的な目標は、
Rustで書いた関数やクラス(的なもの?)をPythonから気軽に呼べるようになること
です。
このPyO3(gitはここ)は、開発の真っ只中であり、バージョン更新が(おそらく)凄まじい速度で行われております。
今回の目標
そもそもRust初見の状態からスタートして、
PythonからRustで書いた関数を呼べるようになる、という目標のもとPyO3を触ってみました。
キータにも、他ブログでもいくつかの解説記事が出ております。
- [Rust] PyO3 で Python パッケージを作成
- Pyo3でPythonとRustを連携させる
- RustモジュールをPythonから実行する (PyO3)
- PythonとRustを使ってPythonの拡張モジュールを書く
今回は、
RustモジュールをPythonから実行する (PyO3)
さんのコードをお借りして、それにsetup.pyを付け加えて、Rustで書かれた関数をPythonから呼んでみたいと思います。
手順
rustのインストール
1行目を実行するとインストールについて3択で聞かれますがdefaultインストールの1を選んで問題ないかと思います。
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
rust nightlyのインストール
PyO3を使用するために必要になります。
開発中のクレートをたくさん含んだβバージョンと言うようなものでしょうか。
バージョンを確認してみる
このバージョンを今回は使います。
$rustc --version
rustc 1.44.0-nightly (f509b26a7 2020-03-18)
$ rustup --version
rustup 1.21.1 (7832b2ebe 2019-12-20)
rustup install nightly
rustup default nightly
rust のプロジェクトを作る
--lib
をつけることで、ライブラリ用のプロジェクトを作ります。
今回は、プロジェクト名をexample
としました。
cargo new --lib example
$ tree example/
├── Cargo.toml
├── setup.py
├── src
│ └── lib.rs
フォルダ構造はこのようになっています。
Cargo.tomlを設定する
RustがC++などより優れていると言われていることのひとつ、ライブラリの管理の手軽さがここにあります。
Cargo.toml
に必要なライブラリを書くのですが、CMakeLists.txt
とかよりはるかに簡単で見やすいです。
[package]
name = "test"
version = "0.1.0"
edition = "2018"
[lib]
name = "test_library"
crate-type = ["cdylib"]
[dependencies.pyo3]
version = "0.9.1"
features = ["extension-module"]
今回はpyo3をライブラリとして使用し、
作る目的のライブラリは、test_library
です。
すなわち、Pythonで
import test_library
みたいに書きたいわけです。
Rustで関数を作る
とりあえず写経してみる
コードはRustモジュールをPythonから実行する (PyO3)
さんのものを丸々持ってきました。
エラトステネスのふるいの実装だそうです。
//lib.rs
use pyo3::prelude::*;
// This is the test function in Rust, getting prime number, which is called by python as get_prime_numbers
#[pyfunction]
fn get_prime_numbers(n: i32) -> PyResult<Vec<i32>> {
let mut flags = Vec::new();
for _ in 0..n+1 {
flags.push(true);
}
let upper = (n as f32).sqrt().floor() as i32;
for i in 2..upper+1 {
if !flags[i as usize] {
continue;
}
let prime = i;
let mut j = prime * 2;
while j <= n {
flags[j as usize] = false;
j += prime;
}
}
let mut primes = Vec::new();
for i in 2..n+1 {
if flags[i as usize] {
primes.push(i);
}
}
Ok(primes)
}
Rustの構文を細かく解説できるだけの能力がありませんので、
この関数はあっているものとし、どうこれがPythonから呼べる形にラップされているかというところを解説します。
#[pyfunction]
fn get_prime_numbers(n: i32) -> PyResult<Vec<i32>>
をみてみると、
まず#[pyfunction]
のデコレータ的なものがこの関数の引数もしくは返り値にPyO3によりパースされたPythonオブジェクトが入ることを宣言しています。
このget_prime_numbers
関数はint32を引数に取り、それ以下の素数をリストとして返す関数です。
このとき、返り値のところが、PyResult<Vec<i32>>
となっていますが、
このPyResult
はuse pyo3::prelude::*;
から来ています。
また、Vec<i32>
は、下の型の対応表のようになっており、PyResut によってPythonのList型に変換されます。
Rust | Python |
---|---|
i32 , usize 等 |
int |
f32 , f64
|
float |
bool |
bool |
Vec<T> |
list |
String |
str |
HashMap |
dict |
[[Rust] PyO3 で Python パッケージを作成]からの引用 (https://qiita.com/osanshouo/items/671888bdd6afeec1e939)
また、最後の
Ok(primes)
がVec
をlist
へと変換しています。
まとめ
ここで終わると参考文献をほとんどコピペしたものになって申し訳ないのですが、
一度ここで区切ります。
まとめとして、
- Rustのインストールを行った。
- Rustのライブラリ用のプロジェクトを作成した。
- Cargo.tomlを書いた。
- 関数を写経し、Rust->Pythonの型変換を確認した。
以上になります。
次はlib.rs
を追記し、setup.py
も加え実際にライブラリのオブジェクトファイルとしてコンパイルしていきます。
今回はこの辺で。
おわり。