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

Ruby Kaigi 2017 - C how to supercharge your Rub...

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

Avatar for Sameer Deshmukh

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.