the future of jruby - baruco 2013

Post on 31-Aug-2014

2.502 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

The FUTURE of

Tuesday, September 17, 13

Me

• Charles Oliver Nutter

• @headius

• Java developer since 1996

• JRuby developer since 2006

• Red Hat / JBoss polyglot group

Tuesday, September 17, 13

Have you heard ofJRuby?

Tuesday, September 17, 13

Have you triedJRuby?

Tuesday, September 17, 13

Are you usingJRuby?

Tuesday, September 17, 13

What is JRuby?

Tuesday, September 17, 13

Ruby on the JVM

Tuesday, September 17, 13

Ruby on the JVM

I don't like Java so I don't like JRuby

Tuesday, September 17, 13

Ruby on the JVM

I don't like Java so I don't like JRuby

LOL applet

s

Tuesday, September 17, 13

Ruby on the JVM

JVM SUCKS R

OFL

I don't like Java so I don't like JRuby

LOL applet

s

Tuesday, September 17, 13

Ruby on the JVM

JVM SUCKS R

OFLAbstractMetaRubyImplementationFactoryFactoryImpl

I don't like Java so I don't like JRuby

LOL applet

s

Tuesday, September 17, 13

Welcome to Spain

Tuesday, September 17, 13

Welcome to Spain

Tuesday, September 17, 13

Welcome to Spain

Tuesday, September 17, 13

Welcome to Spain

Tuesday, September 17, 13

Welcome to Spain

Tuesday, September 17, 13

Welcome to Spain

Tuesday, September 17, 13

Welcome to Spain

Tuesday, September 17, 13

JRuby is Ruby!!!on the JVM... shhh!

Tuesday, September 17, 13

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

FFITuesday, September 17, 13

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

JRuby 9000 really is the next version...

9k...Coming 2014

Tuesday, September 17, 13

One point release later...

Tuesday, September 17, 13

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

Why JRuby?

Tuesday, September 17, 13

JRuby Team

Tuesday, September 17, 13

JRuby Team

Charlie

Tuesday, September 17, 13

JRuby Team

Charlie Tom

Nick Hiro Marcin Nahi Wayne Subbu DouglasDouglasContribsDouglas

Tuesday, September 17, 13

JRuby Team

Charlie Tom

Nick Hiro Marcin Nahi Wayne Subbu DouglasDouglasContribs

DouglasDouglasOpenJDKDouglasDouglasAndroid

DouglasDouglasJ9DouglasDouglasOther

JVMs

Douglas

Tuesday, September 17, 13

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

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

0ms

75ms

150ms

225ms

300ms

188KB/29MB 27MB/127MB 199MB/238MB

Time per GC versus heap usage

Tim

e pe

r G

C

Heap usage (MRI/JRuby)

Ruby 2.0.0 JRuby

Tuesday, September 17, 13

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 FeaturesJRuby Support

Tuesday, September 17, 13

However...

• JVM development is not fast

• JRuby must move forward

• Constantly improving

Tuesday, September 17, 13

What could be better?

Performance

Native Extensions

Concurrency

Startup Time

Tuesday, September 17, 13

Performance

Tuesday, September 17, 13

Hard to Optimize

• Dynamic calls with lots of overhead

• Dynamic object structure with indirection

• Lots and lots of objects

Tuesday, September 17, 13

Solutions

Tuesday, September 17, 13

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

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

Tuesday, September 17, 13

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

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

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

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

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

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

Other Options

• New IR compiler/runtime in 9k

• Optimize Ruby code before JVM

• Specialize types, elide allocations

Tuesday, September 17, 13

LexicalAnalysisParsing

SemanticAnalysis

Optimization

Bytecode Generation

Interpret

AST

IR Instructions

CFG DFG ...

Existing

New!

Dalvik Generation ...

Tuesday, September 17, 13

1 check_arity(2, 0, -1)2 a(0:0) = recv_pre_reqd_arg(0)3 thread_poll4 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 + cend

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_closure4 thread_poll5 line_num(1)6 c(0:3) = 1:fixnum7 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

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

The Hard Part {

Tuesday, September 17, 13

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

Concurrency

Tuesday, September 17, 13

True Parallellism

Ruby Threads

NativeThreads

Ruby 1.8.7 Ruby 2.0.0

Green Threading

CPU Coresin Use

JRuby

Global LockSingle Thread Real Threading

Tuesday, September 17, 13

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

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

Multicore in JRuby

300MB JRuby Instance

One instance across 10 threads = 300MB

Tuesday, September 17, 13

Multicore in JRuby

300MB JRuby Instance

One instance across 100 threads = 300MB

Tuesday, September 17, 13

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

Unsafe Operations

• Concurrent read+write on core structures

• Non-atomic updates

•@count +=1

•@cache ||= MyCache.new

• Thread pooling

• Coordinating threads

Tuesday, September 17, 13

thread_safe

• Concurrency-safe Hash

• Concurrency-safe Array

require 'thread_safe'

sa = ThreadSafe::Array.newsh = ThreadSafe::Hash.new

Tuesday, September 17, 13

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) # => falsesimon.has_key?(:name) # => true

Tuesday, September 17, 13

atomic

• Atomic value holder

• Safely update current value

• Edit value only if unchanged

• Full CPU-level atomicity guarantees

Tuesday, September 17, 13

require 'atomic'

my_atomic = Atomic.new(0)my_atomic.value # => 0my_atomic.value = 1my_atomic.swap(2) # => 1my_atomic.compare_and_swap(2, 3) # => true, updated to 3my_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

jo

• Threaded implementation of "goroutines"

• "channel" for communication

Tuesday, September 17, 13

# pinger ponger printerdef 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 endend

c = chanjo {pinger(c)} # all on separate threadsjo {ponger(c)}jo {printer(c)}

Tuesday, September 17, 13

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

Native Extensions

Tuesday, September 17, 13

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

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

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

Alternatives

Tuesday, September 17, 13

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

import javax.swing.JFrameimport javax.swing.JLable

frame = JFrame.new("Window")label = JLabel.new("Hello")

frame.add(label)frame.default_close_operation = JFrame::EXIT_ON_CLOSEframe.packframe.visible = true

Tuesday, September 17, 13

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

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

Ruby FFI exampleclass Timeval < FFI::Struct  layout :tv_sec => :ulong, :tv_usec => :ulongend

module LibC  extend FFI::Library  ffi_lib FFI::Library::LIBC  attach_function :gettimeofday, [ :pointer, :pointer ], :intend

t = Timeval.newLibC.gettimeofday(t.pointer, nil)

Tuesday, September 17, 13

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

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

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

  # 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

  # 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

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

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

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

Startup Time

Tuesday, September 17, 13

#1 Pain Point

Tuesday, September 17, 13

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

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

LexicalAnalysisParsing

SemanticAnalysis

Optimization

Bytecode Generation

Interpret

AST

IR Instructions

CFG DFG ...

Existing

MORE

Dalvik Generation ...

Tuesday, September 17, 13

Solutions

Tuesday, September 17, 13

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

GSoC 2012IR Persistence

IR Instructions

CFG DFG ...

file.ir

file.rbcompile

file.ccompile

file.o

Tuesday, September 17, 13

Reflection on GSoC

• Size matters

• # of bytes

• Intern()‘ing of identifiers matter

• Laziness can help a lot

Tuesday, September 17, 13

Defined vs Used MethodsCMD 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

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

Ultimate Startup!

rails new fooJRuby Instance

IR Datarails generate

JRuby Instance

rake db:migrate

JRuby Instance

Compile

Use

Use

Background JVM

Tuesday, September 17, 13

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

Thank You!

• Charles Oliver Nutter

• @headius

• headius@headius.com

• http://blog.headius.com

Tuesday, September 17, 13

top related