Ruby
Go

RubyからGoの関数をつかう → はやい

More than 3 years have passed since last update.

この記事ではRuby2.2.3とGo1.5.1を使用しています。

すごい要約

Go and Ruby-FFI - Code7 Interactiveを読むといい。

Go、はやい

最初にRubyとGoの計算速度を比べるため、フィボナッチ数を単純に計算するやつをそれぞれ書きました。

fib.rb
def fib(n)
  return n if n <= 1
  fib(n - 1) + fib(n - 2)
end

puts fib(40)
fib.go
package main

import "fmt"

func fib(n uint) uint {
    if n <= 1 {
        return n
    }
    return fib(n-1) + fib(n-2)
}

func main() {
    fmt.Println(fib(40))
}

比較のためにできるだけ単純に同じロジックで実装しました。
実行結果は以下です。

Ruby
> time ruby fib.rb
102334155
ruby fib.rb  19.00s user 0.11s system 92% cpu 20.690 total
Go
> time go run fib.go
102334155
go run fib.go  1.24s user 0.25s system 84% cpu 1.758 total
ビルド済みGo
> time ./fib
102334155
./fib  0.80s user 0.01s system 89% cpu 0.903 total

この処理だと圧倒的にGoの方がはやいようです。
「Rubyより、ずっとはやい!!!!!!」

Goを共有ライブラリにする

GoをRubyから利用するためには、まずShared Libraryとしてコンパイルします。
ここらへんは「Golang で Shared Library を出力する。」を参考にさせていただきました。
Goのビルド時に-buildmode=c-sharedオプションを渡すとよしなにしてくれます。

ただ、今のままだとRubyから関数が呼べないのでGoのソースを書き換えます。

fib.go
package main

import "C"

//export fib
func fib(n uint) uint {
    if n <= 1 {
        return n
    }
    return fib(n-1) + fib(n-2)
}

func main() {}

ポイントはCをインポートするのと、//export fibを追記する点です。
Rubyから呼べる関数を明示的に選べます。
main()は実行されませんが、コンパイル上必要なので書いておきます。
これで

> go build -buildmode=c-shared -o fib.so fib.go

fib.so と fib.h がディレクトリに作成されたら成功です。

RubyからGoの関数を呼ぶ

共有ライブラリとしてコンパイルしたGoをRubyから扱うにはFFIを使用します。
そもそもFFIとはForeign function interfaceの略で、ffiというgemがこれをRubyでもいい感じにやるぞ!ってやつです。

まずインストール。

> gem install ffi

Ruby-FFIはRubyのオブジェクトに他言語の関数をアタッチしていく感覚で使用します。

go-fib.rb
require "ffi"

module Fib
  extend FFI::Library
  ffi_lib "fib.so"
  attach_function :fib, [:uint], :uint
end

puts Fib.fib(40)

このattach_function()の引数は、「関数名、[関数の引数型]、戻り値型」です。
ちなみに型として利用できるのはTypes · ffi/ffi Wikiに一覧があります。
今回は数値だったのでそこまで問題はありませんが、文字列をやりとりする場合は、
Goの関数内で受け取ったCのstringをC.GoString(str)としたり、逆にGoのstringをC.CString("Go no string dayo!!!!!")
するなど適宜変換してあげる必要があります。
詳しくはcgo - The Go Programming Languageを参照!!!

Ruby meets Go

はいここまで長かった!
速度の計測です。

RubyOnly
> time ruby fib.rb
102334155
ruby fib.rb  19.00s user 0.11s system 92% cpu 20.690 total
Ruby+Go
> time ruby go-fib.rb
102334155
ruby go-fib.rb  0.85s user 0.06s system 95% cpu 0.953 total

約20倍はやい!!!!!!すごい!!!!!!!!!!!!!!
Go単体での実行に毛が生えた程度になりました!!!!!!!!!!!!!!!!!!
もう「Rubyより、ずっとはやい」なんて言わせないぞ!!!!!!!!

こうやってそれぞれの得意分野でよろしくやっていくのが平和でよいなと思いました。

参考

Golang で Shared Library を出力する。 - Qiita
ffi/ffi
cgo - The Go Programming Language
Go and Ruby-FFI - Code7 Interactive