Rubyの拡張を
Crystalで書いてみる
Writing Ruby extension with Crystal
Hirofumi Wakasugi (@5t111111)
Native Extension
Fetching: nokogiri-1.6.7.1.gem (100%)
Building native extensions. This could take a while...
• 特に実行速度が重要な処理をCで書くことができる
• 既存のCライブラリを利用することができる
RubyはC言語で拡張ライブラリを書くことができる
Crystal C Library Bindings
lib LibC
fun pow(x: Float64, y: Float64) : Float64
fun sqrt(val: Float64) : Float64
end
puts(LibC.pow(10, 10))
puts(LibC.sqrt(4))
end
CrystalでCのバインディングを書くのはとても簡単
うん、それなら
CrystalでRubyの拡張書けそう!
def fibonacci(n)
return n if n <= 1
fibonacci(n - 1) + fibonacci(n - 2)
end
まず、この単純なフィボナッチ数列の関数を
Ruby拡張ライブラリとしてCrystalで書きます
Example 1
lib LibRuby
type VALUE = Void*
$rb_cObject : VALUE
fun rb_define_global_function(name : UInt8*, func : VALUE, VALUE -> VALUE, argc : Int32)
fun rb_num2int(value : VALUE) : Int32
fun rb_int2inum(value : Int32) : VALUE
end
def fibonacci_cr(self : LibRuby::VALUE, value : LibRuby::VALUE)
int_value = LibRuby.rb_num2int(value)
LibRuby.rb_int2inum(fibonacci_cr2(int_value))
end
def fibonacci_cr2(n)
return n if n <= 1
fibonacci_cr2(n - 1) + fibonacci_cr2(n - 2)
end
fun init = Init_extension_with_crystal
GC.init
LibCrystalMain.__crystal_main(0, Pointer(Pointer(UInt8)).null)
LibRuby.rb_define_global_function("fibonacci_cr", ->fibonacci_cr, 1);
end
Example 1 - fibobacci_cr
user system total real
fibonacci (ruby) 16.760000 0.040000 16.800000 ( 16.963310)
fibonacci (crystal) 0.830000 0.010000 0.840000 ( 0.827128)
うん、速い
Example 1
class Takeuchi
def self.tarai(x, y, z)
if y < x
tarai(
tarai(x - 1, y, z),
tarai(y - 1, z, x),
tarai(z - 1, x, y)
)
else
y
end
end
end
たらい回し関数も書いてみます
Example 2
lib LibRuby
type VALUE = Void*
$rb_cObject : VALUE
fun rb_define_class(name : UInt8*, super : VALUE) : VALUE
fun rb_define_module_function(klass : VALUE, name : UInt8*, func : VALUE, VALUE, VALUE, VALUE -> VALUE, argc : Int32)
fun rb_num2int(value : VALUE) : Int32
fun rb_int2inum(value : Int32) : VALUE
end
def tarai(self : LibRuby::VALUE, x : LibRuby::VALUE, y : LibRuby::VALUE, z : LibRuby::VALUE)
int_x = LibRuby.rb_num2int(x)
int_y = LibRuby.rb_num2int(y)
int_z = LibRuby.rb_num2int(z)
LibRuby.rb_int2inum(tarai2(int_x, int_y, int_z))
end
def tarai2(x, y, z)
if y < x
tarai2(
tarai2(x - 1, y, z),
tarai2(y - 1, z, x),
tarai2(z - 1, x, y)
)
else
y
end
end
fun init = Init_extension_with_crystal
GC.init
LibCrystalMain.__crystal_main(0, Pointer(Pointer(UInt8)).null)
rb_class_takeuchi = LibRuby.rb_define_class("TakeuchiCr", LibRuby.rb_cObject)
LibRuby.rb_define_module_function(rb_class_takeuchi, "tarai", ->tarai, 3);
end
Example 2 - TakeuchiCr.tarai
user system total real
tarai (ruby) 19.810000 0.050000 19.860000 ( 20.248620)
tarai (crystal) 0.970000 0.000000 0.970000 ( 0.998764)
うん、速い
Example 2
さあ、つらいのはここから
Tsurami 1
person = LibRuby.rb_define_class("Person", LibRuby.rb_cObject)
LibRuby.rb_define_method(person, "Hello", ->hello, 1);
• Crystalの表現力を活用することが難しい
• 規模が大きくなってくるとかなり見通しが悪くなる
結局、普通に使うとC APIのバインディングでしかない
Tsurami 2
#define RB_FIX2LONG(x) ((long)RSHIFT((SIGNED_VALUE)(x),1))
#define StringValuePtr(v) rb_string_value_ptr(&(v))
• Cから扱わない場合シンボルを宣言するのが大変
• 毎回「ruby.h」とかをがんばって読むことになる
Ruby C APIではマクロが多用される
Tsurami 3
fun rb_define_method(klass : VALUE,
name : UInt8*,
func : VALUE, VALUE -> VALUE,
argc : Int32)
• 関数の型を指定する必要がある
• これ以外の型の関数を渡すとコンパイルエラー
Ruby C API関数の引数が関数ポインタのとき…
crystal_ruby
Write Ruby extensions in Crystal.
https://github.com/manastech/crystal_ruby
• 公式による「Proof of Concept」
• 意識高い
• 最新のCrystalでは動かすのにちょっと修正が要る
Proof of Concept (PoC)
ruby_extension "test_ruby",
class Foo
def foo(a)
"From Crystal!! #{a}"
end
end
ruby_extensionマクロでコード(文字列)を処理
マクロに渡されたコードはParserでパースされる
Proof of Concept (PoC)
class MyVisitor < Visitor
…
def visit(node : ClassDef)
@str << %(_class = Ruby::Class.new "#{node.name}"n)
end
def visit(node : Def)
@str << %(_class.def "#{node.name}", #{node.args.size}, )
@str << %[->(self : LibRuby::VALUE, ]
node.args.each do |arg|
@str << %(_#{arg.name} : LibRuby::VALUE, )
…
end
…
end
ASTノードはVisitorを継承したクラスで処理される
関数ポインタは型をVoid*で定義しておいて、ここで具体的な型として設定される
まともに動かないけど意識高いPoC <3
Matome
• Rubyの拡張をCrystalで書ける、が普通に書くとつらい
• でも、Crystalで拡張を書く利点は大きい
‣ やっぱり速い
‣ Rubyのシンタックスと非常に近い
• 公式がRubyの拡張に対して積極的
御静聴
有難う御座いました
本スライド中のサンプルコード
およびその出力内容は
すべて Crystal 0.10.2 で確認しています
LIVE FOREVER

Rubyの拡張をCrystalで書いてみる

  • 1.
  • 2.
    Native Extension Fetching: nokogiri-1.6.7.1.gem(100%) Building native extensions. This could take a while... • 特に実行速度が重要な処理をCで書くことができる • 既存のCライブラリを利用することができる RubyはC言語で拡張ライブラリを書くことができる
  • 3.
    Crystal C LibraryBindings lib LibC fun pow(x: Float64, y: Float64) : Float64 fun sqrt(val: Float64) : Float64 end puts(LibC.pow(10, 10)) puts(LibC.sqrt(4)) end CrystalでCのバインディングを書くのはとても簡単
  • 4.
  • 5.
    def fibonacci(n) return nif n <= 1 fibonacci(n - 1) + fibonacci(n - 2) end まず、この単純なフィボナッチ数列の関数を Ruby拡張ライブラリとしてCrystalで書きます Example 1
  • 6.
    lib LibRuby type VALUE= Void* $rb_cObject : VALUE fun rb_define_global_function(name : UInt8*, func : VALUE, VALUE -> VALUE, argc : Int32) fun rb_num2int(value : VALUE) : Int32 fun rb_int2inum(value : Int32) : VALUE end def fibonacci_cr(self : LibRuby::VALUE, value : LibRuby::VALUE) int_value = LibRuby.rb_num2int(value) LibRuby.rb_int2inum(fibonacci_cr2(int_value)) end def fibonacci_cr2(n) return n if n <= 1 fibonacci_cr2(n - 1) + fibonacci_cr2(n - 2) end fun init = Init_extension_with_crystal GC.init LibCrystalMain.__crystal_main(0, Pointer(Pointer(UInt8)).null) LibRuby.rb_define_global_function("fibonacci_cr", ->fibonacci_cr, 1); end Example 1 - fibobacci_cr
  • 7.
    user system totalreal fibonacci (ruby) 16.760000 0.040000 16.800000 ( 16.963310) fibonacci (crystal) 0.830000 0.010000 0.840000 ( 0.827128) うん、速い Example 1
  • 8.
    class Takeuchi def self.tarai(x,y, z) if y < x tarai( tarai(x - 1, y, z), tarai(y - 1, z, x), tarai(z - 1, x, y) ) else y end end end たらい回し関数も書いてみます Example 2
  • 9.
    lib LibRuby type VALUE= Void* $rb_cObject : VALUE fun rb_define_class(name : UInt8*, super : VALUE) : VALUE fun rb_define_module_function(klass : VALUE, name : UInt8*, func : VALUE, VALUE, VALUE, VALUE -> VALUE, argc : Int32) fun rb_num2int(value : VALUE) : Int32 fun rb_int2inum(value : Int32) : VALUE end def tarai(self : LibRuby::VALUE, x : LibRuby::VALUE, y : LibRuby::VALUE, z : LibRuby::VALUE) int_x = LibRuby.rb_num2int(x) int_y = LibRuby.rb_num2int(y) int_z = LibRuby.rb_num2int(z) LibRuby.rb_int2inum(tarai2(int_x, int_y, int_z)) end def tarai2(x, y, z) if y < x tarai2( tarai2(x - 1, y, z), tarai2(y - 1, z, x), tarai2(z - 1, x, y) ) else y end end fun init = Init_extension_with_crystal GC.init LibCrystalMain.__crystal_main(0, Pointer(Pointer(UInt8)).null) rb_class_takeuchi = LibRuby.rb_define_class("TakeuchiCr", LibRuby.rb_cObject) LibRuby.rb_define_module_function(rb_class_takeuchi, "tarai", ->tarai, 3); end Example 2 - TakeuchiCr.tarai
  • 10.
    user system totalreal tarai (ruby) 19.810000 0.050000 19.860000 ( 20.248620) tarai (crystal) 0.970000 0.000000 0.970000 ( 0.998764) うん、速い Example 2
  • 11.
  • 12.
    Tsurami 1 person =LibRuby.rb_define_class("Person", LibRuby.rb_cObject) LibRuby.rb_define_method(person, "Hello", ->hello, 1); • Crystalの表現力を活用することが難しい • 規模が大きくなってくるとかなり見通しが悪くなる 結局、普通に使うとC APIのバインディングでしかない
  • 13.
    Tsurami 2 #define RB_FIX2LONG(x)((long)RSHIFT((SIGNED_VALUE)(x),1)) #define StringValuePtr(v) rb_string_value_ptr(&(v)) • Cから扱わない場合シンボルを宣言するのが大変 • 毎回「ruby.h」とかをがんばって読むことになる Ruby C APIではマクロが多用される
  • 14.
    Tsurami 3 fun rb_define_method(klass: VALUE, name : UInt8*, func : VALUE, VALUE -> VALUE, argc : Int32) • 関数の型を指定する必要がある • これ以外の型の関数を渡すとコンパイルエラー Ruby C API関数の引数が関数ポインタのとき…
  • 15.
    crystal_ruby Write Ruby extensionsin Crystal. https://github.com/manastech/crystal_ruby • 公式による「Proof of Concept」 • 意識高い • 最新のCrystalでは動かすのにちょっと修正が要る
  • 16.
    Proof of Concept(PoC) ruby_extension "test_ruby", class Foo def foo(a) "From Crystal!! #{a}" end end ruby_extensionマクロでコード(文字列)を処理 マクロに渡されたコードはParserでパースされる
  • 17.
    Proof of Concept(PoC) class MyVisitor < Visitor … def visit(node : ClassDef) @str << %(_class = Ruby::Class.new "#{node.name}"n) end def visit(node : Def) @str << %(_class.def "#{node.name}", #{node.args.size}, ) @str << %[->(self : LibRuby::VALUE, ] node.args.each do |arg| @str << %(_#{arg.name} : LibRuby::VALUE, ) … end … end ASTノードはVisitorを継承したクラスで処理される 関数ポインタは型をVoid*で定義しておいて、ここで具体的な型として設定される
  • 18.
  • 19.
    Matome • Rubyの拡張をCrystalで書ける、が普通に書くとつらい • でも、Crystalで拡張を書く利点は大きい ‣やっぱり速い ‣ Rubyのシンタックスと非常に近い • 公式がRubyの拡張に対して積極的
  • 20.