Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

1,300 views

Published on

"Writing Ruby extension with Crystal" from Tokyo Crystal Meetup #3 (2016-01-22)

Published in: Technology
  • Be the first to comment

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

  1. 1. Rubyの拡張を Crystalで書いてみる Writing Ruby extension with Crystal Hirofumi Wakasugi (@5t111111)
  2. 2. Native Extension Fetching: nokogiri-1.6.7.1.gem (100%) Building native extensions. This could take a while... • 特に実行速度が重要な処理をCで書くことができる • 既存のCライブラリを利用することができる RubyはC言語で拡張ライブラリを書くことができる
  3. 3. 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のバインディングを書くのはとても簡単
  4. 4. うん、それなら CrystalでRubyの拡張書けそう!
  5. 5. def fibonacci(n) return n if n <= 1 fibonacci(n - 1) + fibonacci(n - 2) end まず、この単純なフィボナッチ数列の関数を Ruby拡張ライブラリとしてCrystalで書きます Example 1
  6. 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. 7. 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
  8. 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. 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. 10. 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
  11. 11. さあ、つらいのはここから
  12. 12. Tsurami 1 person = LibRuby.rb_define_class("Person", LibRuby.rb_cObject) LibRuby.rb_define_method(person, "Hello", ->hello, 1); • Crystalの表現力を活用することが難しい • 規模が大きくなってくるとかなり見通しが悪くなる 結局、普通に使うとC APIのバインディングでしかない
  13. 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. 14. Tsurami 3 fun rb_define_method(klass : VALUE, name : UInt8*, func : VALUE, VALUE -> VALUE, argc : Int32) • 関数の型を指定する必要がある • これ以外の型の関数を渡すとコンパイルエラー Ruby C API関数の引数が関数ポインタのとき…
  15. 15. crystal_ruby Write Ruby extensions in Crystal. https://github.com/manastech/crystal_ruby • 公式による「Proof of Concept」 • 意識高い • 最新のCrystalでは動かすのにちょっと修正が要る
  16. 16. Proof of Concept (PoC) ruby_extension "test_ruby", class Foo def foo(a) "From Crystal!! #{a}" end end ruby_extensionマクロでコード(文字列)を処理 マクロに渡されたコードはParserでパースされる
  17. 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. 18. まともに動かないけど意識高いPoC <3
  19. 19. Matome • Rubyの拡張をCrystalで書ける、が普通に書くとつらい • でも、Crystalで拡張を書く利点は大きい ‣ やっぱり速い ‣ Rubyのシンタックスと非常に近い • 公式がRubyの拡張に対して積極的
  20. 20. 御静聴 有難う御座いました 本スライド中のサンプルコード およびその出力内容は すべて Crystal 0.10.2 で確認しています LIVE FOREVER

×