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

The Future of JRuby - Baruco 2013

headius
September 15, 2013

The Future of JRuby - Baruco 2013

A talk on key areas of future work on JRuby, delivered at Baruco 2013 in Barcelona, Spain.

headius

September 15, 2013
Tweet

More Decks by headius

Other Decks in Technology

Transcript

  1. Me • Charles Oliver Nutter • @headius • Java developer

    since 1996 • JRuby developer since 2006 • Red Hat / JBoss polyglot group Tuesday, September 17, 13
  2. Ruby on the JVM I don't like Java so I

    don't like JRuby Tuesday, September 17, 13
  3. Ruby on the JVM I don't like Java so I

    don't like JRuby LOL applets Tuesday, September 17, 13
  4. Ruby on the JVM JVM SUCKS ROFL I don't like

    Java so I don't like JRuby LOL applets Tuesday, September 17, 13
  5. Ruby on the JVM JVM SUCKS ROFL AbstractMetaRubyImplementationFactoryFactoryImpl I don't

    like Java so I don't like JRuby LOL applets Tuesday, September 17, 13
  6. 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
  7. Roadmap 1.6.0 1.6.1 1.6.2 1.6.3 1.6.7.2 1.7.0.pre1 1.6.4 ... 1.6.8

    ... 1.7.3 1.7.4 1.7.5 1.7.6 ... 9000! Next week or two Tuesday, September 17, 13
  8. 9K Questions • Ruby 2.0 or 2.1-only? • 1.8 support

    gone • 1.9 support gone • Java 7+ only? • New compiler will be... ? Tuesday, September 17, 13
  9. JRuby Team Charlie Tom Nick Hiro Marcin Nahi Wayne Subbu

    Douglas Douglas Contribs Douglas Tuesday, September 17, 13
  10. JRuby Team Charlie Tom Nick Hiro Marcin Nahi Wayne Subbu

    Douglas Douglas Contribs Douglas Douglas OpenJDK Douglas Douglas Android Douglas Douglas J9 Douglas Douglas Other JVMs Douglas Tuesday, September 17, 13
  11. JVM Over Time 0 7.5 15 22.5 30 Java 1.4

    Java 5 Java 6 Java 7 JRuby 1.0.3 (bm_red_black_tree.rb) Tuesday, September 17, 13
  12. Versus MRI 1.8 0 7.5 15 22.5 30 Java 1.4

    Java 5 Java 6 Java 7 JRuby 1.0.3 (bm_red_black_tree.rb) MRI 1.8 Tuesday, September 17, 13
  13. 0ms 75ms 150ms 225ms 300ms 188KB/29MB 27MB/127MB 199MB/238MB Time per

    GC versus heap usage Time per GC Heap usage (MRI/JRuby) Ruby 2.0.0 JRuby Tuesday, September 17, 13
  14. Features Over Time 1.6 1.8.4 1.8.6 1.8.7 1.9.2 1.9.3 2.0

    2.1 Ruby Features JRuby Support Tuesday, September 17, 13
  15. However... • JVM development is not fast • JRuby must

    move forward • Constantly improving Tuesday, September 17, 13
  16. Hard to Optimize • Dynamic calls with lots of overhead

    • Dynamic object structure with indirection • Lots and lots of objects Tuesday, September 17, 13
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. Other Options • New IR compiler/runtime in 9k • Optimize

    Ruby code before JVM • Specialize types, elide allocations Tuesday, September 17, 13
  26. Lexical Analysis Parsing Semantic Analysis Optimization Bytecode Generation Interpret AST

    IR Instructions CFG DFG ... Existing New! Dalvik Generation ... Tuesday, September 17, 13
  27. 1 check_arity(2, 0, -1) 2 a(0:0) = recv_pre_reqd_arg(0) 3 thread_poll

    4 line_num(2) 5 %v_2 = call(+, a(0:0), [1:Fixnum]) 6 return(%v_2) -Xir.passes=LocalOptimizationPass,DeadCodeElimination def foo(a, b) c = 1 d = a + c end 0 check_arity(2, 0, -1) 1 a(0:0) = recv_pre_reqd_arg(0) 2 b(0:1) = recv_pre_reqd_arg(1) 3 %block(0:2) = recv_closure 4 thread_poll 5 line_num(1) 6 c(0:3) = 1:fixnum 7 line_num(2) 8 %v_0 = call(+, a(0:0), [c(0:3)]) 9 d(0:4) = copy(%v_0) 10 return(%v_0) Optimization propagation Tuesday, September 17, 13
  28. 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
  29. 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
  30. True Parallellism Ruby Threads Native Threads Ruby 1.8.7 Ruby 2.0.0

    Green Threading CPU Cores in Use JRuby Global Lock Single Thread Real Threading Tuesday, September 17, 13
  31. Multicore in MRI 200MB MRI Instance 200MB MRI Instance 200MB

    MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance Ten-way concurrency * 200MB = 2GB Tuesday, September 17, 13
  32. Multicore in MRI 200MB MRI Instance 200MB MRI Instance 200MB

    MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 100-way concurrency * 200MB = 20GB 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance 200MB MRI Instance Tuesday, September 17, 13
  33. Multicore in JRuby 300MB JRuby Instance One instance across 10

    threads = 300MB Tuesday, September 17, 13
  34. Multicore in JRuby 300MB JRuby Instance One instance across 100

    threads = 300MB Tuesday, September 17, 13
  35. 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
  36. Unsafe Operations • Concurrent read+write on core structures • Non-atomic

    updates •@count +=1 •@cache ||= MyCache.new • Thread pooling • Coordinating threads Tuesday, September 17, 13
  37. thread_safe • Concurrency-safe Hash • Concurrency-safe Array require 'thread_safe' sa

    = ThreadSafe::Array.new sh = ThreadSafe::Hash.new Tuesday, September 17, 13
  38. 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
  39. atomic • Atomic value holder • Safely update current value

    • Edit value only if unchanged • Full CPU-level atomicity guarantees Tuesday, September 17, 13
  40. 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
  41. # 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. import javax.swing.JFrame import javax.swing.JLable frame = JFrame.new("Window") label = JLabel.new("Hello")

    frame.add(label) frame.default_close_operation = JFrame::EXIT_ON_CLOSE frame.pack frame.visible = true Tuesday, September 17, 13
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. require "ffi/gen" FFI::Gen.generate( module_name: "Clang", ffi_lib: "clang", headers: ["clang-c/Index.h"], cflags:

    `llvm-config --cflags`.split(" "), prefixes: ["clang_", "CX"], output: "clang-c/index.rb" ) Tuesday, September 17, 13
  54. # 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
  55. # 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. Lexical Analysis Parsing Semantic Analysis Optimization Bytecode Generation Interpret AST

    IR Instructions CFG DFG ... Existing MORE Dalvik Generation ... Tuesday, September 17, 13
  62. 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
  63. GSoC 2012 IR Persistence IR Instructions CFG DFG ... file.ir

    file.rb compile file.c compile file.o Tuesday, September 17, 13
  64. Reflection on GSoC • Size matters • # of bytes

    • Intern()‘ing of identifiers matter • Laziness can help a lot Tuesday, September 17, 13
  65. 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
  66. 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
  67. 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
  68. 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
  69. Thank You! • Charles Oliver Nutter • @headius • [email protected]

    • http://blog.headius.com Tuesday, September 17, 13