2. Me
• Charles Oliver Nutter
• @headius
• Java developer since 1996
• JRuby developer since 2006
• Red Hat / JBoss polyglot group
Tuesday, September 17, 13
8. Ruby on the JVM
I don't like Java so I don'tlike JRuby
Tuesday, September 17, 13
9. Ruby on the JVM
I don't like Java so I don'tlike JRuby
LOL applets
Tuesday, September 17, 13
10. Ruby on the JVM
JVM SUCKS ROFL
I don't like Java so I don'tlike JRuby
LOL applets
Tuesday, September 17, 13
11. Ruby on the JVM
JVM SUCKS ROFL
AbstractMetaRubyImplementationFactoryFactoryImpl
I don't like Java so I don'tlike JRuby
LOL applets
Tuesday, September 17, 13
20. The Basics
• Compatible with Ruby 1.8.7, 1.9.3
• Mostly written in (clean) Java
• More and more in Ruby going forward
• Entire world of JVM libraries available
JVM
JDK Classes Other Libraries
JRuby Core Classes JRuby Runtime
More Core Classes Standard Lib Extras
Your Application
FFI
Tuesday, September 17, 13
28. JRuby Team
Charlie Tom
Nick Hiro Marcin Nahi Wayne Subbu DouglasDouglasContribsDouglas
Tuesday, September 17, 13
29. JRuby Team
Charlie Tom
Nick Hiro Marcin Nahi Wayne Subbu DouglasDouglasContribs
DouglasDouglasOpenJDK
DouglasDouglasAndroid
DouglasDouglasJ9
DouglasDouglasOther
JVMs
Douglas
Tuesday, September 17, 13
37. Hard to Optimize
• Dynamic calls with lots of overhead
• Dynamic object structure with indirection
• Lots and lots of objects
Tuesday, September 17, 13
39. Compile to Bytecode
• JVM likes JVM bytecode (surprise!)
• Simple compilation of Ruby
• Let JVM do the work
• Can we do better?
Tuesday, September 17, 13
40. Invokedynamic
• New JVM feature for languages
• Bytecode + IR to describe calls
• JVM patches straight through
• Optimize any kind of call like Java
• Ruby as fast as Java...in theory
Tuesday, September 17, 13
42. 0 1 2 3 4
ruby-1.9.3 + Ruby
ruby-2.0.0 + Ruby
maglev + Ruby
macruby-0.12 + Ruby
rbx-2.0.0rc1 + Ruby
ruby-1.9.3 + C ext
ruby-2.0.0 + C ext
jruby + Ruby
jruby + Java ext
red/black tree, pure Ruby versus native
Runtime per iteration
Tuesday, September 17, 13
43. 0 1 2 3 4
ruby-1.9.3 + Ruby
ruby-2.0.0 + Ruby
maglev + Ruby
macruby-0.12 + Ruby
rbx-2.0.0rc1 + Ruby
ruby-1.9.3 + C ext
ruby-2.0.0 + C ext
jruby + Ruby
jruby + Java ext
3.96s
2.48s
red/black tree, pure Ruby versus native
Runtime per iteration
Tuesday, September 17, 13
44. 0 1 2 3 4
ruby-1.9.3 + Ruby
ruby-2.0.0 + Ruby
maglev + Ruby
macruby-0.12 + Ruby
rbx-2.0.0rc1 + Ruby
ruby-1.9.3 + C ext
ruby-2.0.0 + C ext
jruby + Ruby
jruby + Java ext
3.96s
2.48s
1.39s
1.19s
red/black tree, pure Ruby versus native
Runtime per iteration
Tuesday, September 17, 13
45. 0 1 2 3 4
ruby-1.9.3 + Ruby
ruby-2.0.0 + Ruby
maglev + Ruby
macruby-0.12 + Ruby
rbx-2.0.0rc1 + Ruby
ruby-1.9.3 + C ext
ruby-2.0.0 + C ext
jruby + Ruby
jruby + Java ext
3.96s
2.48s
1.39s
1.19s
0.51s
0.51s
0.51s
red/black tree, pure Ruby versus native
Runtime per iteration
Tuesday, September 17, 13
46. 0 1 2 3 4
ruby-1.9.3 + Ruby
ruby-2.0.0 + Ruby
maglev + Ruby
macruby-0.12 + Ruby
rbx-2.0.0rc1 + Ruby
ruby-1.9.3 + C ext
ruby-2.0.0 + C ext
jruby + Ruby
jruby + Java ext
3.96s
2.48s
1.39s
1.19s
0.51s
0.51s
0.51s
0.29s
0.1s
red/black tree, pure Ruby versus native
Runtime per iteration
Tuesday, September 17, 13
47. But...
• Indy was really slow in first Java 7 release
• Got fast in 7u2...and turned out broken
• Rewritten for 7u40
• Slow to warm up
• Getting reports that there's still issues
• Java 8 due in March
Tuesday, September 17, 13
48. Other Options
• New IR compiler/runtime in 9k
• Optimize Ruby code before JVM
• Specialize types, elide allocations
Tuesday, September 17, 13
51. Other Options
• Truffle/Graal
• New compiler backends from Oracle
• Graal = direct API to native JIT
• Truffle = magic optimizing AST atop Graal
• Ruby on Truffle 5x-6x faster than JRuby
• But...
Tuesday, September 17, 13
53. Sooo....
• Keep working with JVM guys on InDy
• Get our own optimizing compiler done
• Explore Graal/Truffle backend
• Compiler geeks wanted! :-)
Tuesday, September 17, 13
58. Multicore in JRuby
300MB JRuby
Instance
One instance across 10 threads = 300MB
Tuesday, September 17, 13
59. Multicore in JRuby
300MB JRuby
Instance
One instance across 100 threads = 300MB
Tuesday, September 17, 13
60. But...
• Ruby world is still growing up
• Concurrency tools being created
• Libraries being made threadsafe
• We need to do more to help
Tuesday, September 17, 13
62. thread_safe
• Concurrency-safe Hash
• Concurrency-safe Array
require 'thread_safe'
sa = ThreadSafe::Array.new
sh = ThreadSafe::Hash.new
Tuesday, September 17, 13
63. hamster
• Persistent collections for Ruby
• A la Clojure and others
simon = Hamster.hash(:name => "Simon", :gender => :male)
simon[:name] # => "Simon"
simon.get(:gender) # => :male
james = simon.put(:name, "James") # => {:name => "James", :gender => :male}
simon # => {:name => "Simon", :gender => :male}
james[:name] # => "James"
simon[:name] # => "Simon"
male = simon.delete(:name) # => {:gender => :male}
simon # => {:name => "Simon", :gender => :male}
male.has_key?(:name) # => false
simon.has_key?(:name) # => true
Tuesday, September 17, 13
64. atomic
• Atomic value holder
• Safely update current value
• Edit value only if unchanged
• Full CPU-level atomicity guarantees
Tuesday, September 17, 13
65. require 'atomic'
my_atomic = Atomic.new(0)
my_atomic.value # => 0
my_atomic.value = 1
my_atomic.swap(2) # => 1
my_atomic.compare_and_swap(2, 3) # => true, updated to 3
my_atomic.compare_and_swap(2, 3) # => false, current is not 2
my_atomic = Atomic.new(0)
my_atomic.update {|v| v + 1}
begin
my_atomic.try_update {|v| v + 1}
rescue Atomic::ConcurrentUpdateError => cue
# deal with it (retry, propagate, etc)
end
Tuesday, September 17, 13
67. # pinger ponger printer
def pinger(c)
20.times { c << 'ping' }
end
def ponger(c)
20.times { c << 'pong' }
end
def printer(c)
40.times do
puts c.take
sleep 1
end
end
c = chan
jo {pinger(c)} # all on separate threads
jo {ponger(c)}
jo {printer(c)}
Tuesday, September 17, 13
68. Bottom Line
• Concurrency can work in Ruby
• Use the right tools and patterns
• Immutability FTW
• Test your apps and libs on JRuby!
Tuesday, September 17, 13
70. Why Not Ruby?
• Performance
• Fine grained (lots of calls down to C)
• Coarse grained (toss work over the wall)
• Library access
Tuesday, September 17, 13
71. JRuby 1.6 C Exts
• Limited support (now disabled)
• Will be moved to external gem
• If you want it, support it
• Some stuff worked...most didn’t
Tuesday, September 17, 13
72. Problems
• Performance
• Data copying to emulate raw structs
• Locking to keep C code thread-safe
• Multiple JRuby instances in one JVM
• No way from C to know which one
• Huge API to support
Tuesday, September 17, 13
74. Java Integration
• Call Java (Scala, Clojure, ...) from Ruby
• Smart mapping of method names
• Type conversions as appropriate
• Super easy and fun
Tuesday, September 17, 13
76. Java Native Extensions
• Similar to C ext for MRI, but with Java
• Fast call protocol...basically free
• Same GC for all objects
• Have to keep in sync if C version too
Tuesday, September 17, 13
77. FFI
• Ruby API/DSL for calling native code
• Runs on all Ruby impls
• Maintained by JRuby team!
• Solves "access" use case
• Works well for coarse-grained calls
Tuesday, September 17, 13
78. Ruby FFI example
class Timeval < FFI::Struct
layout :tv_sec => :ulong,
:tv_usec => :ulong
end
module LibC
extend FFI::Library
ffi_lib FFI::Library::LIBC
attach_function :gettimeofday,
[ :pointer, :pointer ],
:int
end
t = Timeval.new
LibC.gettimeofday(t.pointer, nil)
Tuesday, September 17, 13
79. But...
• Struct binding issues
• Across OSes (also 32+64 bit archs)
• Across library versions
• Library compile option mismatches
• Fine-grained perf sometimes suffers
Tuesday, September 17, 13
80. Ruby FFI Generator
• https://github.com/neelance/ffi-gen
• Clang-based Ruby FFI generator
• Used to generate clang binding it uses
• It's meta!
Tuesday, September 17, 13
82. # A single translation unit, which resides in an index.
class TranslationUnitImpl < FFI::Struct
layout :dummy, :char
end
# Identifies a specific source location within a translation
# unit.
#
# Use clang_getExpansionLocation() or clang_getSpellingLocation()
# to map a source location to a particular file, line, and column.
#
# = Fields:
# :ptr_data ::
# (Array<FFI::Pointer(*Void)>)
# :int_data ::
# (Integer)
class SourceLocation < FFI::Struct
layout :ptr_data, [:pointer, 2],
:int_data, :uint
end
Tuesday, September 17, 13
83. # Retrieves the source location associated with a given file/line/column
# in a particular translation unit.
#
# @method get_location(tu, file, line, column)
# @param [TranslationUnitImpl] tu
# @param [FFI::Pointer(File)] file
# @param [Integer] line
# @param [Integer] column
# @return [SourceLocation]
# @scope class
attach_function :get_location, :clang_getLocation,
[TranslationUnitImpl, :pointer, :uint, :uint],
SourceLocation.by_value
Tuesday, September 17, 13
84. XNI
• Ruby + plain old C
• Covers access and perf cases
• Cross-implementation support
• Struct mapping in compile phase
• Experimental
https://github.com/wmeissner/xni
Tuesday, September 17, 13
85. hitimes C Ext
/**
* call-seq:
* interval.start -> boolean
*
* mark the start of the interval. Calling start on an already started
* interval has no effect. An interval can only be started once. If the
* interval is truely started +true+ is returned otherwise +false+.
*/
VALUE hitimes_interval_start( VALUE self )
{
hitimes_interval_t *i;
VALUE rc = Qfalse;
Data_Get_Struct( self, hitimes_interval_t, i );
if ( 0L == i->start_instant ) {
i->start_instant = hitimes_get_current_instant( );
i->stop_instant = 0L;
i->duration = -1.0l;
rc = Qtrue;
}
return rc;
}
Tuesday, September 17, 13
86. hitimes XNI
/**
* call-seq:
* interval.start -> boolean
*
* mark the start of the interval. Calling start on an already started
* interval has no effect. An interval can only be started once. If the
* interval is truely started +true+ is returned otherwise +false+.
*/
bool hitimes_interval_start( RubyEnv* env, hitimes_interval_t* i )
{
if ( 0L == i->start_instant ) {
i->start_instant = hitimes_get_current_instant( );
i->stop_instant = 0L;
i->duration = -1.0l;
return true;
}
return false;
}
Tuesday, September 17, 13
89. Hard Problem
• MRI boot time is 95% native
• JRuby boot time is 0% native code
• Mostly Java, which needs to warm up
• Parser, interpreter, core classes, compiler
• Even if our code is better, we start slow
Tuesday, September 17, 13
90. Child Processes
• Reduce need for sub-Ruby invokes
• rails -> clean rails env in child
• rails/rake -> bundler relaunch
• rake test -> 4+ processes in Rails
• rake -> rspec in subprocess
• Fix requires changing many libraries
Tuesday, September 17, 13
93. Nailgun/Drip
• Always running background JVM
• Not quite production quality
• signals, IO
• Small Ruby scripts very fast
• Rails not much faster
• Lots of requires, objects, boot logic
Tuesday, September 17, 13
94. GSoC 2012
IR Persistence
IR Instructions
CFG DFG ...
file.ir
file.rb
compile
file.c
compile
file.o
Tuesday, September 17, 13
95. Reflection on GSoC
• Size matters
• # of bytes
• Intern()‘ing of identifiers matter
• Laziness can help a lot
Tuesday, September 17, 13
96. Defined vs Used Methods
CMD DEFINED USED SAVINGS
-e ‘:foo’ 501 33 ~93%
gem install
rails
1897 529 ~72%
rails scaffold 9411 1647 ~82%
rake
db:migrate
9397 1662 ~82%
rake spec 4595 904 ~80%
Tuesday, September 17, 13
97. New IR Persistence
• Binary format
• Constant pool to intern only once per id
• (currently once per occurrence)
• Incremental loading of method bodies
Tuesday, September 17, 13
98. Ultimate Startup!
rails new foo
JRuby Instance
IR Data
rails generate
JRuby Instance
rake db:migrate
JRuby Instance
Compile
Use
Use
Background
JVM
Tuesday, September 17, 13
99. Ruby is Strong
• Still growing and improving
• MRI too!
• Concurrency can be done
• C extensions are holding us back
• Never surrender!
Tuesday, September 17, 13
100. ThankYou!
• Charles Oliver Nutter
• @headius
• headius@headius.com
• http://blog.headius.com
Tuesday, September 17, 13