Various solutions exist (partly)
●
Ruby inline.
– Doesn’t scale.
●
FFI.
– Reductive and manual compilation.
●
SWIG.
– Evil, unreadable wrappers.
●
Helix.
– Entirely new language/paradigm.
Slide 26
Slide 26 text
Ideal solution:
Super-fast
(and nice) Ruby.
Slide 27
Slide 27 text
No content
Slide 28
Slide 28 text
Rubex: A Ferrari for Ruby
Ruby C extensions without losing
your happiness.
Slide 29
Slide 29 text
Improvements from last year
Slide 30
Slide 30 text
Improvements from last year
●
Slide 31
Slide 31 text
Improvements from last year
●
Slide 32
Slide 32 text
Improvements from last year
●
●
Rubex is a much more robust and stable
language.
●
Lots of refactoring of internal codebase.
●
Little shift in Rubex’s goals - from simply speed
to portability/readability of C extensions.
Slide 33
Slide 33 text
What you think of C APIs of gems
Your ruby library’s C ext
Another C ext API that
you are using
Slide 34
Slide 34 text
What it is actually feels like
Your ruby library’s C ext
Another C ext API that
you are using
Slide 35
Slide 35 text
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
Slide 36
Slide 36 text
Rubex code
C code
CRuby runtime
Language which looks like
Ruby.
C Code ready to interface
with Ruby VM.
Code actually runs here.
Slide 37
Slide 37 text
No content
Slide 38
Slide 38 text
["a", "b", "see", "d" ... ]
Slide 39
Slide 39 text
{
"a" => 0,
"b" => 1,
"see" => 2,
"d" => 4
...
}
Slide 40
Slide 40 text
No content
Slide 41
Slide 41 text
array.each_with_index.to_h
Slide 42
Slide 42 text
No content
Slide 43
Slide 43 text
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
Slide 44
Slide 44 text
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
Slide 45
Slide 45 text
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
Slide 46
Slide 46 text
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
Slide 47
Slide 47 text
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
struct blanket
int warmth_factor
char* owner
float len, breadth
end
Slide 56
Slide 56 text
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
Slide 57
Slide 57 text
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
Slide 58
Slide 58 text
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
Slide 59
Slide 59 text
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
Slide 60
Slide 60 text
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
Slide 61
Slide 61 text
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
Slide 62
Slide 62 text
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
_
Slide 63
Slide 63 text
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
Slide 64
Slide 64 text
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
Slide 65
Slide 65 text
Rubex struct wrapping
●
~3x reduction in LoC written.
●
Friendly, elegant Ruby-like interface.
●
No compromise in speed.
●
No C code!
Slide 66
Slide 66 text
Codebase
management through
name-spacing and
public APIs
Slide 67
Slide 67 text
Seamlessly define
C and Ruby functions
in class/module
namespaces.
Slide 68
Slide 68 text
class Foo
cfunc void bar(int a, b)
# some C and Ruby intermix
end
def baz(float c, e)
bar(1, e)
end
end
Slide 69
Slide 69 text
class Foo
cfunc void bar(int a, b)
# some C and Ruby intermix
end
def baz(float c, e)
bar(1, e)
end
end
Slide 70
Slide 70 text
class Foo
cfunc void bar(int a, b)
# some C and Ruby intermix
end
def baz(float c, e)
bar(1, e)
end
end
Slide 71
Slide 71 text
Define public APIs
Slide 72
Slide 72 text
APIs exist in
separate .rubexd
files
Slide 73
Slide 73 text
class Klass
cfunc void foo(int a, int b)
def bar
def baz(int a, b, float c)
end
class OtherKlass
cfunc void foo(int a, int b)
def bar
def baz(int a, b, float c)
end
Slide 74
Slide 74 text
Advantages
●
Easily import C extension APIs through a
‘require_rubex’ compiler declaration.
●
Supply only the compiled binary and API files to
like most C libraries.
●
Portal implementations across Operating
Systems.
●
Auto-generated packaging and compiling
scripts.
Slide 75
Slide 75 text
The infamous GIL
Slide 76
Slide 76 text
No content
Slide 77
Slide 77 text
No content
Slide 78
Slide 78 text
A simple file
reading example
Slide 79
Slide 79 text
Read a file of 5_00_000
lines with a value at each
line into memory
Read line
0 – 1_25_000
Read line
1_25_000 –
2_50_00
Read line
2_50_000 –
3_75_000
Read line
3_75_000 –
5_00_00
Compute sum of
values at each line
Compute sum of
values at each line
Compute sum of
values at each line
Compute sum of
values at each line
Get all values and
compute the average
Slide 80
Slide 80 text
Read a file of 5_00_000
lines with a value at each
line into memory
Read line
0 – 1_25_000
Read line
1_25_000 –
2_50_00
Read line
2_50_000 –
3_75_000
Read line
3_75_000 –
5_00_00
Compute sum of
values at each line
Compute sum of
values at each line
Compute sum of
values at each line
Compute sum of
values at each line
CPU 1 CPU 2 CPU 3 CPU 4
Slide 81
Slide 81 text
Read a file of 5_00_000
lines with a value at each
line into memory
Read line
0 – 1_25_000
Read line
1_25_000 –
2_50_00
Read line
2_50_000 –
3_75_000
Read line
3_75_000 –
5_00_00
Compute sum of
values at each line
Compute sum of
values at each line
Compute sum of
values at each line
Compute sum of
values at each line
Global Interpreter Lock
CPU 1 CPU 2 CPU 3 CPU 4
Slide 82
Slide 82 text
Rubex no_gil block:
A simple way to
release the GIL
Slide 83
Slide 83 text
# In a rubex file test.rubex
cfunc void _some_computation
int i, j, k = 0
no_gil
# ... perform some computation
end
end
Slide 84
Slide 84 text
# In a calling Ruby script caller.rb
require ‘compiled_binary.so’
def compute_without_gil
t = []
4.times {
t << Thread.new {
_some_computation
}
}
4.times { t.join }
end
Slide 85
Slide 85 text
Actual implementation
●
Made a simple implementation of the
aforementioned example of reading and
computing values from a file.
●
Benchmarks indicate huge difference in
performance.
Slide 86
Slide 86 text
Warming up --------------------------------------
without GIL in C 3.000 i/100ms
with GIL in Ruby 1.000 i/100ms
with GIL in C 1.000 i/100ms
Calculating -------------------------------------
without GIL in C 36.210 (± 2.8%) i/s - 183.000 in 5.059510s
with GIL in Ruby 0.102 (± 0.0%) i/s - 1.000 in 9.830386s
with GIL in C 18.591 (± 0.0%) i/s - 93.000 in 5.005381s
Comparison:
without GIL in C: 36.2 i/s
with GIL in C: 18.6 i/s - 1.95x slower
with GIL in Ruby: 0.1 i/s - 355.96x slower
Slide 87
Slide 87 text
See my CPUs!
Slide 88
Slide 88 text
See my code!
https://github.com/v0dro/rubex_csv_reader
Slide 89
Slide 89 text
Limitations of GIL release
●
Can only use C data structures inside the no_gil
block.
●
Overhead associated with releasing and
regaining GIL.
●
Might break code that depends on the GIL.
Slide 90
Slide 90 text
Exception Handling
Slide 91
Slide 91 text
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.
Slide 92
Slide 92 text
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.
Slide 93
Slide 93 text
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
Slide 94
Slide 94 text
https://github.com/sciruby/rubex
Slide 95
Slide 95 text
Differences from Ruby
●
Must specify brackets for function calls.
●
No support for blocks/closures (yet).
●
Must specify return keyword to return from
functions.
●
No support for ‘value of’ operator *.
●
No support for -> operator for struct pointers.
Differences from C
Slide 96
Slide 96 text
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
Slide 97
Slide 97 text
Detailed Docs and Tutorial
●
REFERENCE.md.
– Complete specification of the entire
language.
●
TUTORIAL.md.
– Quick, easy to use explanation with
code samples.
Slide 98
Slide 98 text
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.
●
Provides abstractions in C extensions at
no performance cost.
Slide 99
Slide 99 text
New ideas for a
better Ruby
Slide 100
Slide 100 text
Rubex ideas
●
Typed memory views.
– Get a ‘memory view’ of contiguous Ruby types.
– Will work with NMatrix and NArray gems.
●
Direct interfacing with GPUs through native
kernels.
– Zero-abstraction interfacing with GPUs for
accelerating computation.
– Possible use in cumo.
●
Integration with GDB.
Slide 101
Slide 101 text
Rubyplot – advanced ruby plotting
library
●
Ruby does not have a single native plotting
solution that even comes close to the likes of
matplotlib/bokeh/something else.
●
Rubyists don’t have a single go-to solution for
their visualization needs that can scale.
●
I think this situation is ridiculous for such a
mature language ecosystem.
Slide 102
Slide 102 text
Various partial solutions exist
●
Matplotlib.rb – interfaces python matplotlib via
pycall.
●
Nyaplot – Bokeh like web visualization but
abandoned by author.
●
Google charts/high charts/etc. – too much
dependence on 3rd party web tools, some of
which are paid/non-free.
●
Various GNU plot frontends.
Slide 103
Slide 103 text
Rubyplot can change that!
●
A native plotting solution written in C++ with a
Ruby wrapper.
●
Will directly interface with image-magick, GTK
and GR to create a powerful plotting tool.
●
Unlike matplotlib, will be eventually a language
neutral C++ library to leverage contributions
from other language communities.
Slide 104
Slide 104 text
View the progress of rubyplot
●
Development started a few weeks ago.
●
Follow on discourse:
– https://discourse.ruby-data.org/
●
Follow on GitHub:
– https://github.com/sciruby/rubyplot
●
Contributions/opinions are welcome!
Slide 105
Slide 105 text
Common array library
●
Nmatrix and numo/narray are two major array
libraries.
●
Important to bridge this divide and build on a
library that is robust and well supported.
●
Potential answer is plures – a language
independent C backend to numpy.
Slide 106
Slide 106 text
More about plures
●
Plures is supported by Quansight by the
creators of numpy (Python).
●
Common C API across languages/frameworks.
●
Need more discussion on Ruby frontend.
Slide 107
Slide 107 text
Acknowledgements
●
Ruby Association Grant 2016.
●
Kenta Murata, Koichi Sasada and
Naotoshi Seo for their support and
mentorship.
●
Fukuoka Ruby Award 2016.
●
Ruby Science Foundation.