概要
「実践Rustプログラミング入門」pp.421-427を参照して、RubyからRustの関数を呼ぶ。
そういえば今日(2020年9月4日(金))はRuby Kaigi。
Windows用の準備
RubyInstaller for Windowsから、Windows用のRubyをダウンロードする。
本記事執筆時点最新はRuby+Devkit 2.7.1-1 (x64)。
"If unsure press ENTER"とあるのでENTERで進む。
"Install MSYS2 and MINGW developent toolchain succeeded"とのことなので、ENTERは、3を押したのと同じことだったか。
ここでENTERを押して、インストール終了。
Windows PowerShell上でrubyの動作を確認する。
PS > ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x64-mingw32]
VSCodeでもTERMINALを開きなおせば確認できる。
p.421 サンプル:RubyからRustの関数を呼ぶ。
p.421の実験を行うためのプロジェクトディレクトリを作る。
PS > cargo new ffitest
Created binary (application) `ffitest` package
ffitestの下にsampleディレクトリを作り、Rubyのコードを書いて、Rubyの処理を実験する。
def add_array(n,x)
a = Array.new(n,0)
x.times do
for i in 0..x-1
a[i] += 1
end
end
a.sum
end
puts add_array(ARGV[0].to_i, ARGV[1].to_i)
Measure-Commandは、Linuxでのtime
相当のコマンド。
PS ffitest\sample> Measure-Command {ruby add_array.rb 10000 10000}
Days : 0
Hours : 0
Minutes : 0
Seconds : 8
Milliseconds : 53
Ticks : 80530169
TotalDays : 9.32062141203704E-05
TotalHours : 0.00223694913888889
TotalMinutes : 0.134216948333333
TotalSeconds : 8.0530169
TotalMilliseconds : 8053.0169
TotalSecondsから、約8秒かかっていることがわかる。
つづいてRust側。src/main.rsをsrc/add_array.rsにRenameする。
fn add_array(n: u64, x: u64) -> u64 {
let mut a = vec![0u64; n as usize];
for _ in 0..x {
for i in 0..n as usize {
a[i] += 1;
}
}
a.iter().sum()
}
use std::env;
fn main() {
let args: Vec<_> = env::args().collect();
let n = args[1].parse::<u64>().unwrap();
let x = args[2].parse::<u64>().unwrap();
println!("{}", add_array(n, x));
}
Cargo.tomlに、add_arrayの実行設定を追記する。
[[bin]]
name = "add_array"
path = "src/add_array.rs"
p.422最下段のビルド&実行確認を行う。
PS ffitest> cargo build --release
Compiling ffitest v0.1.0 (ffitest)
Finished release [optimized] target(s) in 1.29s
PS ffitest> Measure-Command {./target/release/add_array 10000 10000}
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 93
Ticks : 935455
TotalDays : 1.0827025462963E-06
TotalHours : 2.59848611111111E-05
TotalMinutes : 0.00155909166666667
TotalSeconds : 0.0935455
TotalMilliseconds : 93.5455
0.09秒。さすがに速い。
p.423 ライブラリ化
ライブラリのプロジェクトディレクトリを作成する。
PS > cargo new --lib addarray
Created library `addarray` package
Cargo.tomlに動的ライブラリ指定を行う。
[lib]
crate-type = ["cdylib"]
ここまでの作業で、src/lib.rsが作成されている。中身は以下の通り。
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
上記のコードは消去し、p.424の2つめのlib.rs通りに記述する。
#[no_mangle]
pub extern "C" fn add_array(n: u64, x: u64) -> u64 {
let mut a = vec![0u64; n as usize];
for _ in 0..x {
for i in 0..n as usize {
a[i] += 1;
}
}
a.iter().sum()
}
ビルドする。いろいろ思考錯誤した結果、--target=x86_64-pc-windows-msvc
とすると、今回のRubyで動かせるDLLになるようだ(調査不足)。
PS > cargo build --release --target=x86_64-pc-windows-msvc
Compiling addarray v0.1.0 (addarray)
Finished release [optimized] target(s) in 0.87s
出来たライブラリを確認する。
PS addarray> ls .\target\x86_64-pc-windows-msvc\release\
ディレクトリ: addarray\target\x86_64-pc-windows-msvc\release
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2020/09/04 --:-- .fingerprint
d----- 2020/09/04 --:-- build
d----- 2020/09/04 --:-- deps
d----- 2020/09/04 --:-- examples
d----- 2020/09/04 --:-- incremental
-a---- 2020/09/04 --:-- 0 .cargo-lock
-a---- 2020/09/04 --:-- 109 addarray.d
-a---- 2020/09/04 --:-- 128512 addarray.dll
-a---- 2020/09/04 --:-- 980 addarray.dll.exp
-a---- 2020/09/04 --:-- 1942 addarray.dll.lib
-a---- 2020/09/04 --:-- 937984 addarray.pdb
addarray.dllができていればOK。
p.425 Rubyからの呼び出し
Rubyのffiをインストールする。
PS > gem install ffi
Fetching ffi-1.13.1-x64-mingw32.gem
Successfully installed ffi-1.13.1-x64-mingw32
Parsing documentation for ffi-1.13.1-x64-mingw32
Installing ri documentation for ffi-1.13.1-x64-mingw32
Done installing documentation for ffi after 1 seconds
1 gem installed
Rubyのソースコードは、addarray/sample/add_array_rs.rbとして作成する。DLLのパス指定に注意。相対パス指定していたらハマったので、絶対パス指定にした。
require 'ffi'
module AddArray
extend FFI::Library
ffi_lib 'C:\your\path\addarray\target\x86_64-pc-windows-msvc\release\addarray.dll'
attach_function :add_array, [:uint64, :uint64], :uint64
end
puts AddArray::add_array(ARGV[0].to_i, ARGV[1].to_i)
p.426 実行確認
PS addarray\sample> Measure-Command {ruby .\add_array_rs.rb 10000 10000}
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 311
Ticks : 3111198
TotalDays : 3.60092361111111E-06
TotalHours : 8.64221666666667E-05
TotalMinutes : 0.00518533
TotalSeconds : 0.3111198
TotalMilliseconds : 311.1198
0.3秒。0.09秒からすると遅くはなるが、もともと8秒だったのだから素晴らしい改善。
番外編:Windows用Ruby&Rust連携のための環境設定
Windows上で動作させる場合は、Rubyの環境に合わせてRust側が適切なDLLを作成しなければならない。という落とし穴にハマり、調査した雑記。
MINGW64のtargetを追加する場合。
PS > rustup target add x86_64-pc-windows-gnu
info: downloading component 'rust-std' for 'x86_64-pc-windows-gnu'
info: installing component 'rust-std' for 'x86_64-pc-windows-gnu'
info: Defaulting to 500.0 MiB unpack ram
14.1 MiB / 14.1 MiB (100 %) 10.9 MiB/s in 1s ETA: 0s
rustupを使って、targetの一覧を確認する。
PS addarray> rustup show
Default host: x86_64-pc-windows-msvc
rustup home: .rustup
installed targets for active toolchain
--------------------------------------
i686-pc-windows-gnu
x86_64-pc-windows-gnu
x86_64-pc-windows-msvc
active toolchain
----------------
stable-x86_64-pc-windows-msvc (default)
rustc 1.45.2 (d3fb005a3 2020-07-31)
cargo build
すると、「x86_64-w64-mingw32-gccがみつからない」と怒られるケースがあった。PATHを追加する。
PS > $ENV:Path="C:\Ruby27-x64\msys64\mingw64\bin;"+$ENV:Path
ビルドする。
PS addarray> cargo build --release --target=x86_64-pc-windows-gnu --verbose
Fresh addarray v0.1.0 (addarray)
Finished release [optimized] target(s) in 0.02s
addarray.dllができている。
PS addarray> ls .\target\x86_64-pc-windows-gnu\release
ディレクトリ: addarray\target\x86_64-pc-windows-gnu\release
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2020/09/04 --:-- .fingerprint
d----- 2020/09/04 --:-- build
d----- 2020/09/04 --:-- deps
d----- 2020/09/04 --:-- examples
d----- 2020/09/04 --:-- incremental
-a---- 2020/09/04 --:-- 0 .cargo-lock
-a---- 2020/09/04 --:-- 108 addarray.d
-a---- 2020/09/04 --:-- 3689956 addarray.dll
-a---- 2020/09/04 --:-- 2056 libaddarray.dll.a
add_array_rs.rb修正&動作確認
改めて、Rubyコード内のDLL指定を変更する。
require 'ffi'
module AddArray
extend FFI::Library
ffi_lib 'C:\your\path\addarray\target\x86_64-pc-windows-gnu\release\addarray.dll'
attach_function :add_array, [:uint64, :uint64], :uint64
end
puts AddArray::add_array(ARGV[0].to_i, ARGV[1].to_i)
反省
- Windowsで実験するときは、実行環境に注意する。mingwや32ビット、64ビットなど、いろいろと落とし穴がある。