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

JRuby and Invokedynamic - Japan JUG 2015

headius
December 08, 2015

JRuby and Invokedynamic - Japan JUG 2015

A talk on how JRuby uses invokedynamic to optimize Ruby, delivered at the Japan Java User's Group hosted by Oracle on December 8, 2015.

headius

December 08, 2015
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. Me • Charles Oliver Nutter • @headius, [email protected] • JVM

    language developer at Red Hat • JRuby co-lead since 2005
  2. Agenda • Brief intro to JRuby (and Ruby) • Overview

    of invokedynamic • Invokedynamic examples • JRuby + invokedynamic
  3. +

  4. Ruby • Created in 1995 by Yukihiro Matsumoto • Dynamically

    typed (dynamic calls) • Object-oriented • Everything is an object • Tight integration with C, UNIX
  5. Ruby • Created in 1995 by Yukihiro Matsumoto • Dynamically

    typed (dynamic calls) • Object-oriented • Everything is an object • Tight integration with C, UNIX
  6. class Hello
 def initialize(name)
 @name = name
 end
 
 def

    display
 puts "Hello, #{@name}"
 end
 end
 
 hello = Hello.new
 hello.display
  7. Command Number of simple calls jruby -e 1 1k gem

    install rails 315k rails new testapp 606k rails simple CRUD 16k
  8. def foo
 bar
 end
 
 def bar
 baz
 end
 


    def baz
 # ...
 end foo baz bar
  9. def foo
 bar
 end
 
 def bar
 baz
 end
 


    def baz
 # ...
 end foo bar baz JRuby call logic JRuby call logic Stops many JVM optimizations JRuby before invokedynamic JRuby call logic
  10. public abstract class CachingCallSite extends CallSite {
 
 protected CacheEntry

    cache = CacheEntry.NULL_CACHE;
 
 private final String methodName = ... 
 public IRubyObject call(...) {
 RubyClass selfType = getClass(self);
 
 CacheEntry cache = this.cache;
 
 if (CacheEntry.typeOk(cache, selfType)) {
 return cache.method.call(...);
 }
 
 return cacheAndCall(...);
 }
 
 protected IRubyObject cacheAndCall(...) {
 CacheEntry entry = selfType.searchWithCache(methodName);
 
 DynamicMethod method = entry.method;
 
 if (methodMissing(method, caller)) {
 return callMethodMissing(context, self, method);
 }
 
 updateCache(entry);
 
 return method.call(context, self, selfType, methodName);
 }
 }
  11. def foo
 bar
 end
 
 def bar
 baz
 end
 


    def baz
 # ...
 end foo bar baz JRuby call logic JRuby call logic Stops many JVM optimizations JRuby before invokedynamic JRuby call logic
  12. "In the future, we will consider bounded extensions to the

    Java Virtual Machine to provide better support for other languages." - JVM Specification First Edition (1997), preface
  13. Goals of InvokeDynamic • A new bytecode • Fast function

    pointers + adapters • Caching and invalidation • Flexible enough for future uses
  14. Invoke // Static System.currentTimeMillis() Math.log(1.0) // Virtual "hello".toUpperCase() System.out.println() //

    Interface myList.add("happy happy") myRunnable.run() // Constructor and super new ArrayList() super.equals(other)
  15. // Static invokestatic java/lang/System.currentTimeMillis:()J invokestatic java/lang/Math.log:(D)D // Virtual invokevirtual java/lang/String.toUpperCase:()Ljava/lang/String;

    invokevirtual java/io/PrintStream.println:()V // Interface invokeinterface java/util/List.add:(Ljava/lang/Object;)Z invokeinterface java/lang/Runnable.add:()V // Special invokespecial java/util/ArrayList.<init>:()V invokespecial java/lang/Object.equals:(java/lang/Object)Z invokestatic invokevirtual invokeinterface invokespecial
  16. invokestatic 1. Confirm arguments are of correct type 2. Look

    up method on Java class 3. Cache method 4. Invoke method invokevirtual 1. Confirm object is of correct type 2. Confirm arguments are of correct type 3. Look up method on Java class 4. Cache method 5. Invoke method invokeinterface 1. Confirm object’s type implements interface 2. Confirm arguments are of correct type 3. Look up method on Java class 4. Cache method 5. Invoke method invokespecial 1. Confirm object is of correct type 2. Confirm arguments are of correct type 3. Confirm target method is visible 4. Look up method on Java class 5. Cache method 6. Invoke method invokedynamic 1. Call your language's logic 2. Install target function 3. Target function invoked directly until you change it
  17. Method Handles • Function/field/array pointers • Argument manipulation • Flow

    control • Optimizable by the JVM • This is very important
  18. java.lang.invoke •MethodHandles • Utilities for acquiring, adapting handles •MethodType •

    Representation of args + return type •MethodHandle • An invokable target + adaptations
  19. MethodHandles.Lookup • Used to get function pointers •MethodHandles.lookup() • Obeys

    visibility of lookup() location • Can expose private members
  20. MethodHandles.Lookup • Method pointers •findStatic, findVirtual,
 findSpecial, findConstructor • Field

    pointers •findGetter, findSetter,
 findStaticGetter, findStaticSetter
  21. // can access everything visible from here MethodHandles.Lookup LOOKUP =

    MethodHandles.lookup(); // can access only public fields and methods MethodHandles.Lookup PUBLOOKUP = MethodHandles.publicLookup();
  22. String value1 = System.getProperty("foo"); MethodHandle m1 = lookup .findStatic(System.class, "getProperty",

    MethodType.methodType(String.class, String.class)); String value2 = (String)m2.invoke("foo"); Static Method
  23. // example Java String value1 = System.getProperty("java.home"); // getProperty signature

    MethodType type1 = MethodType.methodType(String.class, String.class); MethodHandle getPropertyMH = LOOKUP .findStatic(System.class, "getProperty", type1); // invoke String value2 = (String) getPropertyMH.invoke("java.home");
  24. // example Java System.out.println("Hello, world"); // println signature MethodType type2

    = MethodType.methodType(void.class, Object.class); MethodHandle printlnMH = LOOKUP .findVirtual(PrintStream.class, "println", type2); // invoke printlnMH.invoke(System.out, (Object) "Hello, world");
  25. Adapters • java.lang.invoke.MethodHandles.* • Argument manipulation, modification • Flow control

    and exception handling • Similar to writing your own command- pattern utility objects
  26. // insert is like partial application MethodHandle getJavaHomeMH = MethodHandles.insertArguments(getPropertyMH,

    0, "java.home"); // same as getProperty("java.home") getJavaHomeMH.invokeWithArguments();
  27. Flow Control • guardWithTest is a boolean branch • Three

    targets • Condition ("if") • True path ("then") • False path ("else")
  28. // boolean branch // example Java class UpperDowner { public

    String call(String inputString) { if (randomBoolean()) { return inputString.toUpperCase(); } else { return inputString.toLowerCase(); } } }
  29. // randomly return true or false MethodHandle upOrDown = LOOKUP.findStatic(

    BasicHandles.class, "randomBoolean", methodType(boolean.class)); // guardWithTest calls boolean handle and branches MethodHandle upperDowner = guardWithTest( upOrDown, toUpperCaseMH, toLowerCaseMH); upperDowner.invoke("Hello, world"); // HELLO, WORLD upperDowner.invoke("Hello, world"); // hello, world upperDowner.invoke("Hello, world"); // HELLO, WORLD upperDowner.invoke("Hello, world"); // HELLO, WORLD upperDowner.invoke("Hello, world"); // hello, world
  30. Bootstrap • First time JVM sees invokedynamic • Call your

    bootstrap code with name, type • Install resulting CallSite • Subsequent times • Just invoke call site contents
  31. CallSite • Holds a MethodHandle • Returned to JVM by

    bootstrap method • Replaces invokedynamic bytecode • JVM watches it for changes
  32. public static CallSite simpleBootstrap( MethodHandles.Lookup lookup, String name, MethodType type)

    throws Exception { // Create and bind a constant site, pointing at the named method return new ConstantCallSite( lookup.findStatic(SimpleBinding.class, name, type)); }
  33. public static void first(...) { ... System.out.println("first!"); } public static

    void second(...) { ... System.out.println("second!"); } callable.call(); // => "first!" callable.call(); // => "second!" callable.call(); // => "first!" callable.call(); // => "second!"
  34. public static CallSite mutableCallSiteBootstrap( MethodHandles.Lookup lookup, String name, MethodType type)

    throws Exception { MutableCallSite mcs = new MutableCallSite(type); // look up the first method to call MethodHandle target = <get handle to "first" method> // add MutableCallSite into args target = insertArguments(target, 0, mcs); mcs.setTarget(target); return mcs; }
  35. public static void first( MethodHandles.Lookup lookup, MutableCallSite mcs) throws Exception

    { MethodHandle second = <get handle to "second" method> mcs.setTarget(second); System.out.println("first!"); }
  36. public static void second( MethodHandles.Lookup lookup, MutableCallSite mcs) throws Exception

    { MethodHandle first = <get handle to "first" method> mcs.setTarget(first); System.out.println("second!"); }
  37. All Together • Bytecode gets call site • Method handle

    points at target method • Call site caches it • Guard ensures it's the right method
  38. Dynamic Invocation • The obvious one • Method lookup based

    on runtime types • Potentially mutable types • Type check specific to language
  39. class Hello
 def initialize(name)
 @name = name
 end
 
 def

    display
 puts "Hello, #{@name}"
 end
 end
 
 hello = Hello.new
 hello.display
  40. def foo
 bar
 end
 
 def bar
 baz
 end
 


    def baz
 # ...
 end foo bar baz JRuby call logic JRuby call logic Stops many JVM optimizations JRuby before invokedynamic JRuby call logic
  41. def foo
 bar
 end
 
 def bar
 baz
 end
 


    def baz
 # ...
 end foo bar baz JRuby call logic JRuby call logic Dynamic call logic built into JVM JRuby on Java 7 JRuby call logic X X
  42. Dynamic Invocation Target Object Method Table def foo ... def

    bar ... associated with obj.foo() JVM def foo ... Call Site
  43. def foo
 bar
 end
 
 def bar
 baz
 end
 


    def baz
 # ...
 end foo bar baz Straight through dispatch path JRuby on Java 7
  44. def foo
 bar
 end
 
 def bar
 baz
 end
 


    def baz
 # ...
 end foo bar baz Optimizations (like inlining) can happen! JRuby on Java 7
  45. Lazy Constants • Call site just produces a value •

    Value calculated once • Subsequent access is direct, optimizable • Used for numbers, strings, regexp
  46. class Hello
 def initialize(name)
 @name = name
 end
 
 def

    display
 puts "Hello, #{@name}"
 end
 end
 
 hello = Hello.new
 hello.display
  47. Ruby Constants • Ruby's constants can be modified • ...but

    it is very rare • Look up value • Guard against it changing
  48. class Hello
 def initialize(name)
 @name = name
 end
 
 def

    display
 puts "Hello, #{@name}"
 end
 end
 
 hello = Hello.new
 hello.display
  49. Constant Lookup Look up "Foo" constant 10m times 0 0.3

    0.6 0.9 1.2 Time in seconds MRI JRuby Control
  50. Instance Variables • Ruby objects grow as needed • Look

    up offset for @var in object • Cache offset, guard against other types • Direct access to variable table
  51. class Hello
 def initialize(name)
 @name = name
 end
 
 def

    display
 puts "Hello, #{@name}"
 end
 end
 
 hello = Hello.new
 hello.display
  52. Instance Variables Look up @foo variable 10M times 0 0.15

    0.3 0.45 0.6 Time in seconds MRI JRuby Control
  53. class Hello
 def initialize(name)
 @name = name
 end
 
 def

    display
 puts "Hello, #{@name}"
 end
 end
 
 hello = Hello.new
 hello.display
  54. bench_fractal bench_flipflop_fractal • Mandelbrot generator • Integer loops • Floating-point

    math • Julia generator using flip-flops • I don’t really understand it.
  55. bench_fractal 0s 0.4s 0.8s 1.2s 1.6s Iteration 0 1 2

    3 4 5 6 7 Ruby 2.2.2 JRuby + indy
  56. Times Faster than Ruby 2.2.2 0 1 2 3 4

    base64 richards neural 3.66 3.44 2.658 1.914 1.538 1.346 JRuby/Java 6 JRuby/Java 7
  57. red/black tree, pure Ruby versus C ext ruby-2.2.2 + Ruby

    ruby-2.2.2 + C ext jruby + Ruby Runtime per iteration 0 0.75 1.5 2.25 3 0.29s 0.51s 2.48s
  58. Future Work • More creative use of invokedynamic in JRuby

    • Smarter compiler above JVM byte code • Continued JVM optimization