Rubyのパフォーマンスが足りない時に、C言語で書かれたライブラリを呼び出すことが便利です。
ただ、Rubyが使えるC言語のライブラリの書き方は、普通のライブラリと少し異なり、
シンプルな二分探索を実装しながら、CとRubyのインターフェースを可能とするRuby C Apiを紹介したいと思います。
Makefileの作成
まず、Rubyが必要とする複数のC言語のファイルを自動で作成してもらいました。
作業するフォルダにて、extconf.rbというファイルに、以下の内容を記入しました。
#!/usr/bin/env ruby require 'mkmf' # preparation for compilation goes here create_header create_makefile 'c_alg'
extconf.rbをrubyで実行すると、extconf.hとMakefileが作成されます。
今回のライブラリですと、そのファイルの上書きまたは変更が不要です。
加えて、二分探索の関数自体が入っている、c_alg.cというファイルを手動で作成しなければなりません。
データ型周り
C言語はそのままRubyの変数を読み込むことができません。
同様に、C言語から返される変数は、そのままRubyのスクリプトに使われるとおかしい値が出てしまいます。
原則として、Rubyのスクリプトが渡す変数は、C言語から見ると、Ruby C Apiが定義するVALUEというデータ型になります。
ただ、VALUEはRuby C Apiにしか存在しないので、VALUEを、標準Cライブラリを含むその他ライブラリが使えるデータ型(int、char、など)に変換する必要があります。
その際に、NUM2INT, NUM2FLOAT, INT2NUMなどの関数が使えます。
二分探索
以下、c_alg.cにて、Rubyのスクリプトが呼び出せる、とてもシンプルな二分探索のアルゴリズムを実装しました。
#include <ruby.h> // Ruby C Api #include "extconf.h" // Rubyの配列をC言語が読み込める配列に変換 // Rubyは配列を変換する関数を提供してくれないので、手動で処理します。 int *convert_to_c_array(VALUE ruby_array, int len) { int *c_array = (int *)malloc(sizeof(int) * len); for (int i = 0; i < len; i++) { VALUE rb_num = rb_ary_entry(ruby_array, i); c_array[i] = NUM2INT(rb_num); } return c_array; } VALUE c_binary_search(VALUE self, VALUE ruby_array, VALUE ruby_target) { int len = (int)RARRAY_LEN(ruby_array); // RARRAY_LENは配列の長さを返す、Ruby C Apiの関数です。 int target = NUM2INT(ruby_target); int *c_number_array = convert_to_c_array(ruby_array, len); // Cが読み込める配列に変換 int low = 0; int high = len -1; int i = len / 2; while (c_number_array[i] != target) { if (c_number_array[i] < target) { low = i + 1; } else { high = i - 1; } i = low + (high - low) / 2; } // Rubyが読み込める変数に変換 return INT2NUM(i); }
ご覧の通り、データ型の変更以外、あくまでも変哲のないC言語になります。
init関数
今ライブラリをコンパイルし、Rubyのスクリプトでc_binary_search呼び出してもエラーが発生してしまいます。
理由は、c_binary_searchが存在しても、Rubyが使えるものとしてはエクスポートされていないからです。
よって、c_binary_searchをエクスポートする関数を追加しました。
void Init_c_alg() { VALUE module = rb_define_module("CAlg"); // モジュール名を定義 // Rubyのメソッドを定義 // 引数は順番で、モジュール名、Rubyに呼び出される関数名、エクスポートされる関数、selfを除く引数の数 rb_define_method(module, "c_binary_search", c_binary_search, 2); }
今ライブラリをコンパイルし、スクリプトにインポートすれば、なんのエラーもありません。
例えば、ライブラリファイルがあるフォルダにて、以下の内容が記載されているtest.rbファイルを実行すれば1が返されます。
require './c_alg' include CAlg array = [1, 2, 4, 5, 6, 7, 8, 9]; a = c_binary_search(array, 2) print a
終わりに
関数が一つしかない、とてもシンプルなライブラリを作成しました。
もちろん、ファイルや関数が多く、複雑なライブラリの開発も可能です。
ユースケースが多く、とても便利なRubyやRuby on Railsを拡張する方法だと思います。