連記事目次
- Ruby/Rust 連携 (1) 目的
- Ruby/Rust 連携 (2) 手段
- Ruby/Rust 連携 (3) FFI で数値計算
- Ruby/Rust 連携 (4) Rutie で数値計算①
- Ruby/Rust 連携 (5) Rutie で数値計算② ベジエ
- Ruby/Rust 連携 (6) 形態素の抽出
- Ruby/Rust 連携 (7) インストール時ビルドの Rust 拡張 gem を作る
はじめに
Ruby と Rust はある意味で対極にある言語だ。従って,
- 両方を学ぶことでプログラミング観が広がる
- 組み合わせることで両方の特長が生かせる
のではないか,と期待したい。
そこで,何回かに分けて,Ruby と Rust の連携をさぐる記事を書いてみたいと思う。(長続きしない人間なのですぐ止まっちゃうかもしれないけど)
初回は,どんなときに連携すると嬉しいのか,連携の目的について考えてみた。
なお,筆者は細く長い Ruby 人生を送っているが,Rust はいつまで経ってもド素人のままである(というか,ほとんど Rust でプログラムを書いてない)。
システムプログラミングの世界は全く知らないし,そもそもコンパイラー恐怖症に罹患している。
Ruby から Rust を使う
Rust のスピードが欲しい
Ruby でスクリプトを書いていて,「この部分を Rust で書き直したら速くなるんでは?」と思うことがしばしばある。
たとえば,Helix1 のデモでは,ActiveSupport の blank?
を Rust で実装してパフォーマンスを圧倒的に向上させる,といった例が紹介されていた2。
しかし,「(Rust に限らず)システムプログラミング言語で Ruby を置き換えたら(なんでも)速くなる」というのは,よくある幻想だ。
文字列の加工を例に取ると,String#gsub
だの String#downcase
だのといった組込みメソッドは,それ自体は非常に高速に動作する。これらのメソッドをいくつか組み合わせた程度の処理を素人が Rust で書き直したところで勝ち目はない。
「Ruby は遅い」というが,どういう意味で遅いのか。
メソッド呼び出しやブロック評価にはそれなりにコストがかかるので,膨大な回数のイテレーションの中で多数のメソッド呼び出しを組み合わせたような処理を実行する場合,C や Rust で書いたのよりずっと遅くなる,ということはよくあるのだろう。そういうところでは Rust による書き換えを検討する余地があるかもしれない。
Rust による高速化で期待したいのが並行・並列プログラミングだろう。Rust は「Fearless Concurrency」を標榜していて3,並行プログラミングがバグまみれになりにくい仕組みが備わっているようだし,並列性の高い処理だとごく簡単に並列化できるようだ4。
GC を避けたい
スピードが欲しい話と関係するけど,Ruby で極めて大量のガーベジが発生するような処理を Rust 化したらメモリー消費が抑えられる,ということはないだろうか。
ウェブアプリケーションのように動きっぱなしのプログラムでは頻繁にガーベジコレクションが起こるだろう。その頻度が下げられればパフォーマンスが良くなるかもしれない。
ガーベジの発生しやすい処理としては,たとえば,形態素解析がありそうだ。
Ruby にも MeCab や Juman++ その他に基づく形態素解析ライブラリーがあり,手軽に形態素解析が使える。
しかし,たとえばテキスト中から名詞だけ抜き出したいだけの場合でも,余計な文字列が大量に発生する。
というのは,それらのライブラリーから得られる情報は,(MeCab を例に取ると)形態素ごとにたとえば
が 助詞,格助詞,一般,*,*,*,が,ガ,ガ
という文字列になっている(「が」のあとはタブ)。
これをタブとカンマで分割したりすると,いくつもの String オブジェクトができる。そのほとんどは不要なのに。これらはガーベジになる。
で,品詞を "名詞"
と比較して・・・などとやっていると,いかにも効率が悪そうな気がする5。
Rust はガーベジコレクションを使わずにメモリーを管理するので,形態素解析をして必要な情報を抜き出すところを Rust が担当し,結果を Ruby スクリプトに渡すようにすれば,もしかすると効率が良いかもしれない。
Rust 製ソフトウエアが使いたい
パフォーマンス面以外はどうだろう。
Rust 製の優秀なツールやライブラリーを Ruby から使いたい,ということもある。
C 製のソフトウエアを Ruby で使うことはごく当たり前で,いわゆる「拡張ライブラリー」のほとんどは C 製ではないだろうか。
広く使ってもらう拡張ライブラリーを Rust で書くことは,インストール先に Rust コンパイラーが必要という点でハードルが高いけれど,内製ツールなどであれば十分考えられる。
拡張ライブラリーの形でなくとも,ともかく Rust 製ソフトウエアを Ruby で使うことはこれから増えるだろう。
Rust から Ruby を使う
Rust から Ruby を使うことも考えられる。
Ruby だとごく簡潔に書ける処理が,Rust や C,C++ などでは非常に面倒くさい,ということはよくある。そういう処理を部分的に Ruby で書く,というのはありなのかもしれない。
実際,Rutie6 には,Rust 側から Ruby のコードを呼び出す仕組みが備わっているようだ。
Rust 側から呼び出す Ruby は mruby も候補に挙がるかもしれない。
参考:mruby-sys
-
Ruby と Rust を繋ぐ仕組みの一つ。https://github.com/tildeio/helix (2020年10月で deprecated に) ↩
-
パフォーマンスが大きく向上するのはもちろん
blank?
メソッドの話である。Rails アプリケーション全体が爆速化するわけではない。とはいえblank?
は Rails で多数回実行されるので,あなどれないと思う。 ↩ -
私のほとんどゼロに近い Rust 経験のなかでも,rayon を使った並列化をちょこんと入れただけで実行時間が半減したことがあった。 ↩
-
本当に非効率かどうかはきちんと測定してみなければ分からないが。 ↩
-
Ruby と Rust を繋ぐ仕組みの一つ。https://github.com/danielpclark/rutie ↩