こんにちは、akiyamaです。
社内勉強会担当が回ってきました。
弊社で使用されている言語はrubyが主流なので、 今回はruby拡張ライブラリの書き方について発表しました。 ついでなのでcrystalで書きました。
サンプルとして竹内関数を拡張ライブラリ化しました。
(発表時点でcrsytalのバージョンは0.16.0です。 ここに書かれていることは、将来のバージョンでは使用できなくなる可能性があります)
以下、コードと解説です。
def tarai(x, y, z)
if x <= y
y
else
tarai(tarai(x - 1, y, z),
tarai(y - 1, z, x),
tarai(z - 1, x, y))
end
end
lib Ruby
$rb_cObject: Void*
fun rb_define_class(name: LibC::Char*, value: Void*): Void*
fun rb_define_method(klass: Void*,
name: LibC::Char*,
func: LibC::Int, LibC::ULong*, Void* -> LibC::ULong,
argc: LibC::Int)
fun rb_fix2int(value: LibC::ULong) : LibC::Long
end
fun ext_tarai(argc: LibC::Int, args: LibC::ULong*, rb_self: Void*) : LibC::ULong
x = Ruby.rb_fix2int(args[0])
y = Ruby.rb_fix2int(args[1])
z = Ruby.rb_fix2int(args[2])
n = tarai(x, y, z).to_u64
n << 1 | 0x01
end
fun init = Init_rubyext : Void
GC.init
LibCrystalMain.__crystal_main(0, Pointer(Pointer(UInt8)).null)
klass = Ruby.rb_define_class("RubyExtCrystal", Ruby.rb_cObject)
Ruby.rb_define_method(klass, "tarai", ->ext_tarai, -1)
end
lib宣言
呼びたいCの関数やグローバル変数をここで宣言する
fun rb_fix2int(value: LibC::ULong) : LibC::Long
は、Cで言うと
extern long rb_fix2int(unsigned long);
と同じ
ext_tarai
ruby -> cブリッジ部分
Init_rubyext
rubyからload時に呼ばれる初期化関数
- crystalのGCを初期化してcrystal初期化関数を呼ぶ(呼ばないとSEGV)
rb_define_class
でrubyの世界にRubyExtCrystalクラスを定義する- RubyExtCrystalにtaraiメソッドを定義する
- ext_taraiをProc化してコールバック関数として登録する
- 可変長関数を扱うのが面倒なのでarity -1指定して自分で展開する
- argc, argsに引数が入る
build
crystal build --release --single-module --link-flags="-dynamic -bundle -lruby" -o rubyext.bundle rubyext.cr
rubyから呼び出してベンチマーク
require 'benchmark'
require_relative './rubyext'
ext = RubyExtCrystal.new
def tarai(x, y, z)
if x <= y
y
else
tarai(tarai(x - 1, y, z),
tarai(y - 1, z, x),
tarai(z - 1, x, y))
end
end
x,y,z = ARGV.map(&:to_i)
Benchmark.bm 10 do |b|
b.report 'ruby' do
tarai(x, y, z)
end
b.report 'crystal' do
ext.tarai(x, y, z)
end
end
$ ruby tarai.rb 18 10 5
user system total real
ruby 3.990000 0.010000 4.000000 ( 3.999338)
crystal 0.130000 0.000000 0.130000 ( 0.132147)
同等のコードをC言語で書く
#include <stdio.h>
#include <stdlib.h>
int tarai(int x, int y, int z)
{
if (x <= y) {
return y;
} else {
return tarai(
tarai(x - 1, y, z),
tarai(y - 1, z, x),
tarai(z - 1, x, y));
}
}
int main(int argc, char** argv)
{
int x, y, z;
x = atoi(argv[1]);
y = atoi(argv[2]);
z = atoi(argv[3]);
printf("%d %d %d\n", x, y, z);
printf("%d\n", tarai(x, y, z));
retturn 0;
}
$ clang -O2 tarai.c
$ time ./a.out 18 10 5
18 10 5
18
./a.out 18 10 5 0.14s user 0.00s system 96% cpu 0.146 total
ほぼ、おんなじでした