Ruby.EXT.1

ENTRANCEINFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)Developper's Nest(開発掲示板)こころ宙


目次


拡張モジュールって何? 必要なの?

 Ruby 拡張モジュール は (基本的に) C 言語で書かれた、Ruby から呼び出せるプログラムのことです。
 「あれ? Ruby はスクリプト言語じゃなかったの?」という声が聞こえてきそうですが、この拡張モジュールは実行時にコンパイルされたりするようなものではありません。実行前に予めコンパイルしておき、そのコンパイルされたプログラムを使用するのです。Ruby そのものは C 言語で書かれていますが、拡張モジュールは自分でそこにプログラムを書き足し、まさに Ruby を 拡張 してしまうわけです。

 しかしここで「C 言語とかでプログラムを書きたくなかったから Ruby を使っているんじゃないか。なのに、どうしてわざわざ面倒くさい C 言語を使わなければならないのさ?」と思われるかもしれません。確かにその通りで、Ruby ならば数行で終わるような内容も、C 言語で書くと一気に倍増してしまいます。「Ruby で書けば 10 分程度で終わるのに、C 言語で書くと 1 時間もかかってしまう…。」というような事も珍しくありません。
 ですが、実行速度はどうでしょうか。実際には「Ruby で書いたものだと 1 時間かかるのに、C 言語で書いたものだと 10 分もあれば終わってしまう!」という事が珍しくないのです。もし、このプログラムがとても頻繁に使うものだったとしたら…?

 これはとても極端な例です。通常であれば、Ruby は 片手間 に使われることがほとんどなので、それほど速度に関して気を遣う必要はありません。しかし、数メガもあるようなデータを扱う場合や、シミュレーションのように大量の演算を高速に行う必要がある場合、Ruby だけでは速度的に役不足です。「ならば全部 C 言語で書いてしまえばいいじゃないか。」というのもその通りなのですが、すべてを C 言語で記述するとなると、データファイルを読み込み、その書式に合わせてデータを解析し…と 本来の目的と関係ない部分 に多量の労力を費やさなければなりません。しかし、そのような処理は Ruby を使うと簡単に記述できます。
 Ruby だけだと役不足、C 言語で書くと手間がかかる。
 そこで、拡張モジュールの出番となるわけです。
 インタプリタ言語の利便性にコンパイラ言語の高速性が組み合わせられることになり、インタプリタ言語 Ruby だけを扱うよりも、ずっと広い範囲の作業に対応することができるようになるのです。

 それ以外の目的にも拡張モジュールは使われます。

 ひとつは、既存のライブラリが提供している機能を Ruby で扱いたいような場合です。たとえば Ruby に標準添付されている nkf ライブラリですが、これはもともと C 言語のライブラリとして提供されているものです。しかしこの nkf を組み込んだ拡張モジュールが Ruby の標準ライブラリとして用意されているので、その拡張モジュールを読み込むことで、Ruby からも nkf の機能を利用することができるようになっています。

 他には、特定のプラットフォームに独自の機能を使いたいような場合です。Ruby はプラットフォームに依存しないような形になっているため、たとえば Windows GDI を使用するようなプログラムは書くことができません。しかし拡張モジュールで望みの機能を実現するようなプログラムを書いてしまえば、そのような機能を利用することができるようになるのです。もっとも、その環境でしか利用できない拡張モジュールになってしまいますけれども。

 いろいろと説明してきましたが、拡張モジュールというものは、次のように言えると思います。

無くてもいいけど在ると Ruby の可能性がもっと広がるもの

 Ruby を使ってきたけれどもイマイチ肝心な部分で使えなくて困っている方々、Ruby にもっと色々な機能を取り込んで使えるようにしたいと思っている方々、使えると自慢できそうだなぁと企んでいる方々、とにかく何でもいいのですが、ぜひ拡張モジュールを書きこなせるようになって、さらに深い Ruby の世界へ一緒に足を突っ込んでいきましょう!(笑)

(2002/10/16)

拡張モジュールに必要なもの

 拡張モジュールを作るためには、以下のものが必要となります。

C コンパイラ
C 言語で記述するのですから当然ですね。
gcc がよく使われますが、Win32 用のバイナリであれば cl (Visual C++ コンパイラ) が使用されます。Borland C++ Compiler であれば bcc32 となります。
Ruby インタプリタ
これも無ければ話になりません。無いと、単なる C のプログラムになってしまいます…。
以降の話はすべて Ruby 1.6 系 を想定して行います。
make
これが無いと Makefile を処理できず、ビルドできません。ちなみに、Makefile は Ruby が生成してくれるので、Makefile の知識までは必要ありません。ところで Win32 用は Ruby によって nmake 用の Makefile が生成されるのでしょうか? 御存知の方がいらっしゃいましたら、お知らせくださいませ。

これだけです。
 UNIX 系の環境ならば全て標準的にインストールされていますので、とくに何かを用意しなければならないという事はありません。困ってしまうのが Windows 系で、Visual C++ を持っていない場合は拡張モジュールの構築を行うことができません。「そんなお金は持ってないよ!」という方は Cygwin を使うのがいいでしょう。標準的なインストールを行えば gcc と make が入るので、拡張モジュールを構築できるようになります。また、Borland C++ Compiler を使用するという手もあります。ただ、こちらは 1.7 でなければ正しくコンパイルなど行えないようで、使用するためのハードルは少し高めになってしまいます。

 具体的な手順はまだ示しませんが、拡張モジュール開発の流れは大まかに見ると次のようになります。

  1. Makefile を作るための Ruby スクリプトを書く (専用のライブラリが用意されているから簡単です)
  2. C 言語で拡張モジュールを書く
  3. Ruby で拡張モジュールを利用するスクリプトを書く
  4. make して実行形式を生成する
  5. デバッグしてソースコードを修正する
  6. また make して実行形式を生成する
  7. デバッグする…

(2002/10/17)

拡張モジュールを作ってみる

 百聞は一見に如かず。まず、何もわからない状態で構わないので、手順通りに作業を行って拡張モジュールを作成してみましょう。ここで行った手順が再現できない場合は、自分で拡張モジュールを作成することもできないということになってしまいます。自分の環境で拡張モジュール開発が可能かどうかをチェックするという意味でも、以下の作業を行ってみてください。

 まず、適当なディレクトリを作りましょう。

$ mkdir work; cd work
$ mkdir exttest

 そして、以下のファイル extconf.rb を作成します。

require 'mkmf'
create_makefile('exttest')

 次は拡張モジュールのメインとなる exttest.c を作成します。

#include "ruby.h"

int fib ( int n )
{
    if( n >= 3 )
        return fib(n-1) + fib(n-2);
    else
        return 1;
}

VALUE exttest_foo ( VALUE self, VALUE arg )
{
    return INT2FIX(fib(NUM2INT(arg)));
}

void Init_exttest ( void )
{
    rb_define_global_function("foo", exttest_foo, 1);
}

 最後に、テスト用の Ruby プログラム exttest.rb を作成します。

require "exttest.so"

def fib ( n )
  if n >= 3 then
    fib(n-1) + fib(n-2)
  else
    1
  end
end

n = 30
p Time.times
p foo(n)
p Time.times
p fib(n)
p Time.times

 ここまで終わったら、拡張モジュールの構築に入ります。
 まずは Makefile を生成し、それから make を行います。すると、あら不思議、拡張モジュールができあがっています。環境によって出力されるメッセージは異なりますが、以下に示すものは Cygwin の場合です。

$ ruby extconf.rb
creating Makefile

$ make
gcc ... -c -o exttest.o exttest.c
dllwrap ...

 あとは実行するだけです。ちなみに、作成したスクリプトはフィボナッチ数列を求めるプログラムで、わざと再計算を行うような設計になっています。一度計算した値をキャッシュすれば飛躍的に早くなるのですが、ここでは拡張モジュールのパワーを見せつけるために(笑)、そのような小細工は行っていません。
 プログラムを見ればすぐにわかると思いますが、Ruby 版も C 言語版も、同じ計算を行うようになっています。

$ ruby exttest.rb
#<Struct::Tms utime=0.06, stime=0.03, cutime=0.0, cstime=0.0>
832040
#<Struct::Tms utime=0.14, stime=0.03, cutime=0.0, cstime=0.0>
832040
#<Struct::Tms utime=5.297, stime=0.03, cutime=0.0, cstime=0.0>

 Ruby 版も C 言語版も、同じ値を返しています。当然です。
 異なるのは実行時間です。まず C 言語版ですが、実行に 0.08 秒を費やしています。一方の Ruby 版は実行に 5.297 秒も費やしてしまっていて、実に数十倍という速度差が生じてしまっています。この原因はいろいろあるのですが、まあ、簡単に「Ruby はインタプリタ言語で C 言語はコンパイラ言語だから」と言ってしまっていいでしょう。

 さて、ここまで、ちゃんと再現できましたか?
 できなかった場合は、コンパイラや make にちゃんとパスが通っているか、そもそも Ruby がインストールされているか、などをチェックしてみてください。それほど変な理由はないはずです。

 今回の例はあまりにも極端でしたが、これで、あらゆる場面で Ruby が有用なわけではない、ということを少しでも感じることができたのではないでしょうか? Ruby スクリプトのこなせないような仕事に立ち向かうのが拡張モジュールです。もし Ruby をいろいろなところで使いたい、と考えているのでしたら、ぜひこの拡張モジュールの作り方というのを学んでみてください。きっと役に立つはずです。

(2002/10/19)


ENTRANCEINFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)Developper's Nest(開発掲示板)こころ宙