Rubyから他言語を呼んでみた
Rubyから他言語を呼んでみた
GoとRustをライブラリ化してRubyから呼んだ。 ベンチマークとしてフィボナッチ数列の40番目を求めている。
きっかけ
Rubyは書きやすいが、速くない。(Rubyの処理がボトルネックになることは少ないかもしれないが…) 機械学習の人たちはPythonからCやFortranなど速い言語で実装されたライブラリを呼び、爆速で数値計算しているので、Rubyでも似たようなことができるのではないか。
使用した処理系のバージョン
➜ ~ ruby -v ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-darwin15] ➜ ~ go version go version go1.7.4 darwin/amd64 ➜ ~ rustc -V rustc 1.16.0-nightly (47c8d9fdc 2017-01-08)
Rubyでフィボナッチ数を求める実装
def fib(n) if n <= 1 n else fib(n - 1) + fib(n - 2) end end puts fib(40)
例
fib(4) = fib(3) + fib(2) = (fib(2) + fib(1)) + (fib(1) + fib(0)) = ((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0)) = 1 + 0 + 1 + 1 + 0 = 3
➜ time ruby main.rb 102334155 ruby main.rb 13.39s user 0.07s system 99% cpu 13.588 total
- プログラム自体の処理時間
- プログラムを処理するために、OSが処理をした時間
- プログラムの呼び出しから終了までにかかった時間
Goでフィボナッチ数を求める実装
package main import "fmt" func fib(n uint32) uint32 { if n <= 1 { return n } else { return fib(n-1) + fib(n-2) } } func main() { fmt.Println(fib(40)) }
fmt
はprint系関数のパッケージuint32
は0から2**32-1まで
Goをライブラリ化
package main import "C" //export fib func fib(n uint32) uint32 { if n <= 1 { return n } else { return fib(n-1) + fib(n-2) } } func main() {}
➜ go build -buildmode=c-shared -o fib.so lib.go
- Goのビルド時に
-buildmode=c-shared
オプションを渡すとライブラリとしてコンパイルでき、fib.so
とfib.h
が作成される。 //export fib
はただのコメントではない。書かないと上記のコマンドを実行してもfib.h
が作成されない。
require 'ffi' module Fib extend FFI::Library ffi_lib 'fib.so' attach_function :fib, [:uint32], :uint32 end puts Fib.fib(40)
ffi
はForeign Function Interface
の略ffi_lib
の引数はパスattach_function
の引数は関数名,[関数の引数型],戻り値型
➜ time ruby ruby_go.rb 102334155 ruby ruby_go.rb 0.89s user 0.05s system 99% cpu 0.943 total
Rustでフィボナッチ数を求める実装
fn fib(n: u32) -> u32 { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } } fn main() { println!("{}", fib(40)); }
u32
は0から2**32-1まで
Rustをライブラリ化
#[no_mangle] pub extern fn fib(n: u32) -> u32 { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } }
- Cargo.tomlに以下の内容を書く。
[lib] name = "fib" // 任意の名前 crate-type = ["dylib"] // dynamic libraries
➜ cargo build --release
- 上記コマンドを実行すると、
target/release
以下にlibfib.dylib
が作成される。 - CargoはRubyでいうBundler
require 'ffi' module Fib extend FFI::Library ffi_lib 'fib/target/release/libfib.dylib' attach_function :fib, [:uint32], :uint32 end puts Fib.fib(40)
➜ time ruby ruby_rust.rb 102334155 ruby ruby_rust.rb 0.76s user 0.04s system 98% cpu 0.811 total
計測結果
Ruby Only | Ruby + Go | Ruby + Rust |
---|---|---|
13.588s | 0.943s | 0.811s |