⚙️
DenoからFFIでRustを呼び出す
FFIというものを使ったことがなかったので、お試しでDenoからRustの関数を呼び出してみます。
FFIとは
Foreign Function Interface の略で、あるプログラミング言語で書かれたプログラムから、別のプログラミング言語で書かれた関数を呼び出すためのインタフェースです。
プロジェクトの作成
mkdir deno-rust-ffi
cd deno-rust-ffi
ライブラリの作成
今回はライプニッツの公式で円周率を求める関数を作って、せっかくなのでdenoで実装した場合との速度の比較をしてみようかと思います。
cargo init --lib
Cargo.tomlを編集する
[package]
name = "myfirstffi"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
lib.rsを編集する
#[no_mangle]
pub extern "C" fn leibniz(n: u32) -> f64 {
let mut pi = 0.0;
for i in 0..(n+1) {
pi += (-1i32).pow(i) as f64 /(2*i+1) as f64;
}
pi * 4.0
}
#[no_mangle]とは
Rustのアトリビュート(属性)の1つです。
Rustでは、同じ名前の関数が異なるライブラリから定義されることを防ぐために、人間に読みにくい異なる名前に変えるマングリングというのが行われます。
これが行われてしまうとFFIで関数を参照する際に見つからなくなってしまうので、#[no_mangle]
でマングリングが行われないようにします。
ライブラリのビルド
cargo build --release
./target/release/
フォルダの中にlibfirstffi.dylib
というのができているはずです。
firstffi
の部分はCargo.tomlで名付けた名前になり、拡張子の.dylib
は環境によって違うので注意。
❯ ls ./target/release
build
deps
examples
incremental
libmyfirstffi.d
libmyfirstffi.dylib
denoでライブラリを読み込む
main.ts
を作成する。
// 環境ごとに違う拡張子に対応する。
let libSuffix = "";
switch (Deno.build.os) {
case "windows":
libSuffix ="dll";
break;
case "darwin":
libSuffix ="dylib";
break;
default:
libSuffix ="so";
break;
}
// ライブラリを読み込む
const library = Deno.dlopen(`./target/debug/libmyfirstffi.${libSuffix}`, {
leibniz: {
parameters: ["u32"], // 引数の型
result: "f64", // 戻り値の型
}
})
const result = library.symbols.leibniz(100000);
console.log(result);
実行します。
--allow-ffi
でffiの使用を許可し、--unstable
フラッグが必要です。
deno run --allow-ffi --unstable main.ts
無事円周率を求めることができました。
3.1416026534897203
速度の比較
main.ts
に次のように書き加えて速度を比較してみます。
function deno_leibniz(n: number) {
let pi = 0;
for (let i = 0; i < n + 1; i++) {
pi += Math.pow(-1, i) / (2 * i + 1);
}
return pi * 4;
}
console.time("deno_leibniz");
const d = deno_leibniz(1000000);
console.timeEnd("deno_leibniz");
console.log(d);
console.time("rust_leibniz");
const r = library.symbols.leibniz(1000000);
console.timeEnd("rust_leibniz");
console.log(r);
結果!
❯ deno run --allow-ffi --unstable main.ts
deno_leibniz: 12ms
3.1415936535887745
rust_leibniz: 116ms
3.1415936535887745
あれ?denoの方が10倍も早い...?
なんとも締まらない結果になりましたが、FFIの使い方を知りたかったのでとりあえず良しとします。
自分でもまた調べてみますが、denoの方が早かった理由を知っている方は教えていただけると嬉しいです。
Discussion
cargo build
ではなくcargo build --release
でビルドして、target/debug
以下ではなくてtarget/release
以下の共有ライブラリを呼び出すべきですご指摘ありがとうございます。
修正しました。