こんにちは、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ブリッジ部分

rubyから渡ってきた引数(VALUE)を整数値へと変換する VALUEはRubyの世界で値を意味する型(ruby/ruby.h) typedef uintptr_t VALUE; マシンワードサイズ rubyはsizeof(void*) == sizeof(long) or sizeof(LONG_LONG)の環境でしか動かない crystal内ではULong,Void*として取り扱う

tarai関数を呼び出す 次のリターン用に型変換

結果をFIXNUMに変換してリターン INT2NUMなどのCマクロが使用できないので手動変換 rubyのFIXNUMは数値を1bit左シフトして最下位ビットを1にしたもの いわゆるタグ付き C:0b0011 (3) -> ruby:0b0111 (7)



Init_rubyext

rubyからload時に呼ばれる初期化関数

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

" , x , y , z ); printf ( "%d

" , 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

ほぼ、おんなじでした