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を拡張する方法だと思います。