Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby Kaigi 2017 - C how to supercharge your Ruby with Rubex

Ruby Kaigi 2017 - C how to supercharge your Ruby with Rubex

Sameer Deshmukh

September 18, 2017
Tweet

More Decks by Sameer Deshmukh

Other Decks in Programming

Transcript

  1. Various solutions exist (partly) • Ruby inline. – Doesn’t scale.

    • FFI. – Reductive and manual compilation. • SWIG. – Evil, unreadable wrappers. • Helix. – Entirely new language/paradigm.
  2. Ruby vs. Rubex Ruby program Rubex program def add(int a,int

    b) return a + b end def add(a, b) return a + b end
  3. Rubex code C code CRuby runtime Language which looks like

    Ruby. C Code ready to interface with Ruby VM. Code actually runs here.
  4. class Array2Hash def self.convert(arr a) long int i = a.size,

    j = 0 hsh result = {} while j < i do result[a[j]] = j j += 1 end return result end end
  5. class Array2Hash def self.convert(arr a) long int i = a.size,

    j = 0 hsh result = {} while j < i do result[a[j]] = j j += 1 end return result end end
  6. class Array2Hash def self.convert(arr a) long int i = a.size,

    j = 0 hsh result = {} while j < i do result[a[j]] = j j += 1 end return result end end
  7. class Array2Hash def self.convert(arr a) long int i = a.size,

    j = 0 hsh result = {} while j < i do result[a[j]] = j j += 1 end return result end end
  8. class Array2Hash def self.convert(arr a) long int i = a.size,

    j = 0 hsh result = {} while j < i do result[a[j]] = j j += 1 end return result end end
  9. Benchmarks Warming up -------------------------------------- convert 368.000 i/100ms each_with_index.to_h 236.000 i/100ms

    Calculating ------------------------------------- convert 3.488k (± 9.8%) i/s - 17.296k in 5.012260s each_with_index.to_h 2.192k (± 8.3%) i/s - 11.092k in 5.097432s Comparison: convert: 3487.8 i/s each_with_index.to_h: 2192.3 i/s - 1.59x slower
  10. • GC marking of Ruby objects. • Memory deallocation. •

    Write an extconf.rb. • struct rb_data_type_t. • TypedData_Make_Struct(). • TypedData_Get_Struct(). • rb_define_instance_method(). • rb_define_class(). • rb_define_alloc_func().
  11. class BlanketWrapper attach blanket def initialize(warmth_factor, owner, len, breadth) data$.blanket.warmth_factor

    = warmth_factor data$.blanket.owner = owner data$.blanket.len = len data$.blanket.breadth = breadth end def warmth_factor return data$.blanket.warmth_factor end # ... more code for blanket interface. end
  12. class BlanketWrapper attach blanket def initialize(warmth_factor, owner, len, breadth) data$.blanket.warmth_factor

    = warmth_factor data$.blanket.owner = owner data$.blanket.len = len data$.blanket.breadth = breadth end def warmth_factor return data$.blanket.warmth_factor end # ... more code for blanket interface. end
  13. class BlanketWrapper attach blanket def initialize(warmth_factor, owner, len, breadth) data$.blanket.warmth_factor

    = warmth_factor data$.blanket.owner = owner data$.blanket.len = len data$.blanket.breadth = breadth end def warmth_factor return data$.blanket.warmth_factor end # ... more code for blanket interface. end
  14. class BlanketWrapper attach blanket def initialize(warmth_factor, owner, len, breadth) data$.blanket.warmth_factor

    = warmth_factor data$.blanket.owner = owner data$.blanket.len = len data$.blanket.breadth = breadth end def warmth_factor return data$.blanket.warmth_factor end # ... more code for blanket interface. end
  15. class BlanketWrapper attach blanket def initialize(warmth_factor, owner, len, breadth) data$.blanket.warmth_factor

    = warmth_factor data$.blanket.owner = owner data$.blanket.len = len data$.blanket.breadth = breadth end def warmth_factor return data$.blanket.warmth_factor end # ... more code for blanket interface. end
  16. class BlanketWrapper attach blanket def initialize(warmth_factor, owner, len, breadth) data$.blanket.warmth_factor

    = warmth_factor data$.blanket.owner = owner data$.blanket.len = len data$.blanket.breadth = breadth end def warmth_factor return data$.blanket.warmth_factor end # ... more code for blanket interface. end
  17. class BlanketWrapper attach blanket def initialize(warmth_factor, owner, len, breadth) data$.blanket.warmth_factor

    = warmth_factor data$.blanket.owner = owner data$.blanket.len = len data$.blanket.breadth = breadth end def warmth_factor return data$.blanket.warmth_factor end # ... more code for blanket interface. end
  18. class BlanketWrapper attach blanket def initialize(warmth_factor, owner, len, breadth) data$.blanket.warmth_factor

    = warmth_factor data$.blanket.owner = owner data$.blanket.len = len data$.blanket.breadth = breadth end def warmth_factor return data$.blanket.warmth_factor end # ... more code for blanket interface. end
  19. Rubex struct wrapping • ~3x reduction in LoC written. •

    Friendly, elegant Ruby-like interface. • No compromise in speed. • No C code!
  20. Many C functions need to be used • rb_raise() for

    raising error. • rb_rescue(), rb_rescue2(), rb_protect(), rb_ensure() for rescue and ensure blocks. • rb_errinfo() for getting the last error raised. • rb_set_errinfo(Qnil) for resetting error information.
  21. Workflow becomes complex • Almost zero compliance with begin-ensure block

    workflow. • Create C function callbacks. • Manually catch and rescue exceptions. • Inflexibility in sending data to callbacks.
  22. int i = accept_number() begin raise(ArgumentError) if i == 3

    raise(FooBarError) if i == 5 rescue ArgumentError i += 1 rescue FooBarError i += 2 ensure i += 10 end
  23. 3 steps to write libcsv wrapper 1. Tell Rubex about

    the functions /types/constants and header files that you will be using. 2. Use functions in normal Rubex code. 3. Compile and call in your Ruby script.
  24. lib "csv.h", link: "csv" struct csv_parser; end # more types

    ... int CSV_STRICT_FINI # more macros ... int csv_init(csv_parser, unsigned char) size_t csv_parse( csv_parser *p, void *, size_t, void (*cb1)(void *, size_t, void *), void (*cb2)(int, void *), void * ) end
  25. lib "csv.h", link: "csv" struct csv_parser; end # more types

    ... int CSV_STRICT_FINI # more macros ... int csv_init(csv_parser, unsigned char) size_t csv_parse( csv_parser *, void *, size_t, void (*cb1)(void *, size_t, void *), void (*cb2)(int, void *), void * ) end
  26. lib "csv.h", link: "csv" struct csv_parser; end # more types

    ... int CSV_STRICT_FINI # more macros ... int csv_init(csv_parser, unsigned char) size_t csv_parse( csv_parser *, void *, size_t, void (*cb1)(void *, size_t, void *), void (*cb2)(int, void *), void * ) end
  27. lib "csv.h", link: "csv" struct csv_parser; end # more types

    ... int CSV_STRICT_FINI # more macros ... int csv_init(csv_parser, unsigned char) size_t csv_parse( csv_parser *, void *, size_t, void (*cb1)(void *, size_t, void *), void (*cb2)(int, void *), void * ) end
  28. lib "csv.h", link: "csv" struct csv_parser; end # more types

    ... int CSV_STRICT_FINI # more macros ... int csv_init(csv_parser, unsigned char) size_t csv_parse( csv_parser *, void *, size_t, void (*cb1)(void *, size_t, void *), void (*cb2)(int, void *), void * ) end
  29. lib "csv.h", link: "csv" struct csv_parser; end # more types

    ... int CSV_STRICT_FINI # more macros ... int csv_init(csv_parser, unsigned char) size_t csv_parse( csv_parser *, void *, size_t, void (*cb1)(void *, size_t, void *), void (*cb2)(int, void *), void * ) end
  30. lib "csv.h", link: "csv" struct csv_parser; end # more types

    ... int CSV_STRICT_FINI # more macros ... int csv_init(csv_parser, unsigned char) size_t csv_parse( csv_parser *, void *, size_t, void (*cb1)(void *, size_t, void *), void (*cb2)(int, void *), void * ) end
  31. class LibCSVWrapper def self.parse(file_name, opts) # allocate memory, initialize variables

    ... begin if str_len != csv_parse(&cp, string, str_len, &eof_callback, &eol_callback, &meta) # check and raise errors end ensure # free allocated data end # return computed result end end
  32. class LibCSVWrapper def self.parse(file_name, opts) # allocate memory, initialize variables

    ... begin if str_len != csv_parse(&cp, string, str_len, &eof_callback, &eol_callback, &meta) # check and raise errors end ensure # free allocated data end # return computed result end end
  33. class LibCSVWrapper def self.parse(file_name, opts) # allocate memory, initialize variables

    ... begin if str_len != csv_parse(&cp, string, str_len, &eof_callback, &eol_callback, &meta) # check and raise errors end ensure # free allocated data end # return computed result end end
  34. class LibCSVWrapper def self.parse(file_name, opts) # allocate memory, initialize variables

    ... begin if str_len != csv_parse(&cp, string, str_len, &eof_callback, &eol_callback, &meta) # check and raise errors end ensure # free allocated data end # return computed result end end
  35. Notable Rubex examples • Rubex repo examples/ folder. – Fully

    functional libcsv wrapper for reading CSV files written entirely in Rubex. • Array2Hash gem – https://github.com/v0dro/array2hash
  36. Detailed Docs and Tutorial • REFERENCE.md. – Complete specification of

    the entire language. • TUTORIAL.md. – Quick, easy to use explanation with code samples.
  37. Conclusion • Rubex is a fast and productive way of

    writing Ruby C extensions. • Provides users with the elegance of Ruby and the power of C while following the principle of least surprise. • Future work will involve ability to release the GIL, interface with GPUs and Rubex APIs for gems.
  38. Acknowledgements • Ruby Association Grant 2016. • Kenta Murata and

    Koichi Sasada for their support and mentorship. • Fukuoka Ruby Award 2016.