2011/04/25

SWIGを使ってRubyの拡張ライブラリを作る

Rubyは高い生産性を持つ言語ですが、反面パフォーマンスがいまいちです。Rubyで開発していてパフォーマンスが出ずに困った経験はないでしょうか?僕はあります。しかし、だからといってC/C++でスクラッチ開発すると死人が出てしまいます。そんなときは、SWIGを使ってRubyの拡張ライブラリを作ってみましょう。
SWIGはC/C++で書いたプログラムをRubyから呼び出せるようにするためのツールです。SWIGを使えば、全体はRubyで書き、特にパフォーマンスが要求される部分だけをC/C++で書くといったことが可能になります。

環境

環境は以下のとおりです。
  • Mac OS X 10.6.6
  • Ruby 1.8.7 p334
  • gcc 4.2.1
  • SWIG 2.0.3

SWIGのインストール

MacならMacPortsを使って簡単にインストールできます。

$ sudo port install swig
Rubyの拡張ライブラリを作るには、いっしょにRubyバインディングもインストールする必要があります。
$ sudo port install swig-ruby

サンプルプログラム

簡単なサンプルとして、C言語で書いたフィボナッチ数列を計算するプログラムを拡張ライブラリ化してみます。ソースコートは以下のとおりです。

fib.c
int fib(int n) {
    if (n == 0) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }
    return fib(n - 1) + fib(n - 2);
}

fib.h
int fib(int n);

まずSWIG用のインタフェースファイルを書きます。インタフェースファイルには、モジュール名とモジュールから呼び出す関数を宣言します。

fib.i
%module fib

extern int fib(int n);

インタフェースファイルが書けたらswigコマンドを実行します。swigコマンドを実行すると、C言語の関数をRubyから呼び出すためのラッパーファイルを生成してくれます。

$ swig -ruby fib.i

ここまででSWIGの仕事は終わりです。
最後にソースファイルとラッパーファイルを使って共有ライブラリを作ります。Makefileを自分で書くのは面倒なので、extconf.rbに生成してもらいましょう。

extconf.rb
require 'mkmf'
create_makefile("fib")

$ ruby extconf.rb
$ make

C言語の関数をRubyから呼び出してみます。共有ライブラリをrequireすれば、通常のモジュール関数のように呼び出すことができます。
irb(main):001:0> require 'fib'
=> true
irb(main):003:0> puts Fib.fib(10)
55
=> nil
おー。無事呼び出せましたね。

ベンチマーク

ベンチマークを測定し、Rubyで書いたプログラムとパフォーマンスを比較してみました。

bench_fib.rb
#!/opt/local/bin/ruby
require 'fib'
require 'benchmark'

def fib(n)
  return 0 if n == 0
  return 1 if n == 1
  fib(n - 1) + fib(n - 2)
end

n = 30
Benchmark.bm(13) do |x|
  x.report("c fib(#{n}):")    { Fib.fib(n) }
  x.report("ruby fib(#{n}):") { fib(n) }
end

結果は以下のようになりました。やっぱりC言語は速い!

user     system      total        real
c fib(30):     0.010000   0.000000   0.010000 (  0.015184)
ruby fib(30):  2.170000   0.000000   2.170000 (  2.176323)

まとめ

SWIGを使ってRubyの拡張ライブラリを作る方法を紹介しました。SWIGを上手に使えば、Rubyの生産性とC/C++のパフォーマンスを同時に手に入れることが可能になります。Rubyで開発していてパフォーマンスが出ずに悩んでいる人は試してみてはいかがでしょうか。






0 件のコメント:

コメントを投稿