PythonからRustを呼んで高速化する

  • URLをコピーしました!
目次

前置き

PythonからRustを呼んで高速化する方法です
Pyo3は上手くいかなかったので今回は使わない方法を紹介します
そのままのPythonでは呼べないのでCythonを使います
またCythonとRustの仲介役としてC++を使います(文字列の扱いが簡単になる)

Rust

まずは作成

cargo new rust_to_py --lib

libをsrcに名前を変えておいてください

[package]
edition = "2018"
name = "rust_to_py"
version = "0.1.0"

- [lib]
+ [src]
crate-type = ["cdylib"]
name = "rs_library"
use std::{ffi::CStr, os::raw::c_char};

fn _print(code: &str) -> i32 {
   println!("{}",&code);
   return 0;
}

#[no_mangle]
pub extern "C" fn rs_fact(x: i32) -> i32 {
    if x <= 0 {
        return 1;
    }
    return rs_fact(x - 1) * x;
}

#[no_mangle]
// *const c_charはC言語の文字列を受け取る型
pub extern "C" fn rs_print(
    code: *const c_char
) -> i32 {
    return _print(unsafe { CStr::from_ptr(url) }.to_str().unwrap());
}

C++用にヘッダーファイルを作っておきましょう

#pragma once

namespace rs_to_py_lib {
extern "C" {

int rs_fact(int x);

int rs_print(const char *code);

}
}

C++

今回C++は二つのサンプルを用意 文字列を渡す方法 C++経由で呼ぶ方法

//lib/cpp/main.cpp

#include "main.hpp"

#include <iostream>
namespace cpp_main_lib {
int c_rs_print(std::string code) {
    //c_str()でC言語の文字列に変換
    return rs_to_py_lib::rs_print(code.c_str());
}
int c_fact(int x) {
    if (x <= 0) {
        return 1;
    }
    return c_fact(x - 1) * x;
}
int c_rs_fact(int x) { return rs_to_py_lib::rs_fact(x); }
}

しっかり宣言しておきましょう

//lib/include/main.hpp

#pragma once
#include <string>

#include "rs.hpp"
namespace cpp_main_lib {
int c_rs_print(std::string code);
int c_fact(int x);
int c_rs_fact(int x);
}

Cython

直で呼ぶ方法もあるのでサンプルに載せています

# lib/cython/modules.pyx

from libcpp.string cimport string
cdef extern from "../include/main.hpp" namespace "cpp_main_lib":
    cdef int c_rs_print(string code)
cdef extern from "../include/main.hpp" namespace "cpp_main_lib":
    cdef int c_fact(int x)
cdef extern from "../include/main.hpp" namespace "cpp_main_lib":
    cdef int c_rs_fact(int x)
cdef extern from "../include/rs.hpp" namespace "rs_to_py_lib":
    cdef int rs_fact(int x)
# lib/cython/main.pyx

include "modules.pyx"
from libcpp.string cimport string

cpdef int py_c_rs_print(string code):
    return c_rs_print(code)

#一番早い
cpdef int py_c_fact(int x):
    return c_fact(x)

#一番遅い
cpdef int py_c_rs_fact(int x):
    return c_rs_fact(x)

#中間
cpdef int py_rs_fact(int x):
    return rs_fact(x)

Cythonの設定

# setup.py

from distutils.core import setup
from distutils.extension import Extension

from Cython.Distutils import build_ext

sources = ["./lib/cython/main.pyx","./lib/cpp/main.cpp"]

setup(
    cmdclass=dict(build_ext=build_ext),
    ext_modules=[
        Extension(
            name='calc',
            sources=sources,
            language='c++',
            include_dirs=['./lib/include'],
            extra_compile_args=["-stdlib=libc++", "-Wdeprecated"],
            library_dirs=["./target/release"],
            libraries=["rs_library"],
        )
    ]
)

Build

STEP
cargo build --release 
STEP
sudo CFLAGS=-stdlib=libc++ python setup.py build_ext -i 
STEP

するとsetup.pyのnameで指定した値(今回の例だとcalc)でimportできるようになります 文字列はPython3はバイト文字に変換する必要があります
文字列の前にbまたはencode('文字コード')

import calc
x = 2
print(calc.py_c_fact(x))

print(calc.py_c_rs_fact(x))

print(calc.py_rs_fact(x))

print(calc.py_c_rs_print(code.encode('utf-8')))

まとめ

いかがだったでしょうか?
以上PythonからRustを呼んで高速化する方法でした

pc

この記事が気に入ったら
フォローしてね!

  • URLをコピーしました!
目次