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.sofib.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)
  • ffiForeign 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

参考