89
INVOKEDYNAMIC

JRuby and Invokedynamic - Japan JUG 2015

Embed Size (px)

Citation preview

Page 1: JRuby and Invokedynamic - Japan JUG 2015

❤ INVOKEDYNAMIC

Page 2: JRuby and Invokedynamic - Japan JUG 2015

Me

• Charles Oliver Nutter

• @headius, [email protected]

• JVM language developer at Red Hat

• JRuby co-lead since 2005

Page 3: JRuby and Invokedynamic - Japan JUG 2015

Agenda

• Brief intro to JRuby (and Ruby)

• Overview of invokedynamic

• Invokedynamic examples

• JRuby + invokedynamic

Page 4: JRuby and Invokedynamic - Japan JUG 2015
Page 5: JRuby and Invokedynamic - Japan JUG 2015

+

Page 6: JRuby and Invokedynamic - Japan JUG 2015

2001: JRuby is Born

Page 7: JRuby and Invokedynamic - Japan JUG 2015

2005: JRuby on Rails

Page 8: JRuby and Invokedynamic - Japan JUG 2015

2015: JRuby 9000

Page 9: JRuby and Invokedynamic - Japan JUG 2015
Page 10: JRuby and Invokedynamic - Japan JUG 2015

Ruby

• Created in 1995 by Yukihiro Matsumoto

• Dynamically typed (dynamic calls)

• Object-oriented

• Everything is an object

• Tight integration with C, UNIX

Page 11: JRuby and Invokedynamic - Japan JUG 2015

Ruby

• Created in 1995 by Yukihiro Matsumoto

• Dynamically typed (dynamic calls)

• Object-oriented

• Everything is an object

• Tight integration with C, UNIX

Page 12: JRuby and Invokedynamic - Japan JUG 2015

class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display

Page 13: JRuby and Invokedynamic - Japan JUG 2015

Ruby == Method CallsThousands upon thousands of them.

Page 14: JRuby and Invokedynamic - Japan JUG 2015

Command Number of simple calls

jruby -e 1 1k

gem install rails 315k

rails new testapp 606k

rails simple CRUD 16k

Page 15: JRuby and Invokedynamic - Japan JUG 2015

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

foo bazbar

Page 16: JRuby and Invokedynamic - Japan JUG 2015

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

foo bar bazJRubycalllogic

JRubycalllogic

Stops many JVM optimizations

JRuby before invokedynamic

JRubycalllogic

Page 17: JRuby and Invokedynamic - Japan JUG 2015

Invocation

TargetObject

Object’sClassvoid foo()

static void bar()

instanceof

obj.foo() JRuby

void foo()

Page 18: JRuby and Invokedynamic - Japan JUG 2015

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); }}

Page 19: JRuby and Invokedynamic - Japan JUG 2015

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

foo bar bazJRubycalllogic

JRubycalllogic

Stops many JVM optimizations

JRuby before invokedynamic

JRubycalllogic

Page 20: JRuby and Invokedynamic - Japan JUG 2015

Invokedynamic

Page 21: JRuby and Invokedynamic - Japan JUG 2015

"In the future, we will consider bounded extensions to the Java VirtualMachine to provide better support for other languages."

- JVM Specification First Edition (1997), preface

Page 22: JRuby and Invokedynamic - Japan JUG 2015

Goals of InvokeDynamic

• A new bytecode

• Fast function pointers + adapters

• Caching and invalidation

• Flexible enough for future uses

Page 23: JRuby and Invokedynamic - Japan JUG 2015

How does it work?

Page 24: JRuby and Invokedynamic - Japan JUG 2015

Invoke

Page 25: JRuby and Invokedynamic - Japan JUG 2015

Invoke// StaticSystem.currentTimeMillis()Math.log(1.0) // Virtual"hello".toUpperCase()System.out.println() // InterfacemyList.add("happy happy")myRunnable.run() // Constructor and supernew ArrayList()super.equals(other)

Page 26: JRuby and Invokedynamic - Japan JUG 2015

// Staticinvokestatic java/lang/System.currentTimeMillis:()Jinvokestatic java/lang/Math.log:(D)D

// Virtualinvokevirtual java/lang/String.toUpperCase:()Ljava/lang/String;invokevirtual java/io/PrintStream.println:()V

// Interfaceinvokeinterface java/util/List.add:(Ljava/lang/Object;)Zinvokeinterface java/lang/Runnable.add:()V

// Specialinvokespecial java/util/ArrayList.<init>:()Vinvokespecial java/lang/Object.equals:(java/lang/Object)Z

invokestatic

invokevirtual

invokeinterface

invokespecial

Page 27: JRuby and Invokedynamic - Japan JUG 2015

invokestatic1. Confirm arguments are of correct type2. Look up method on Java class3. Cache method4. Invoke method

invokevirtual1. Confirm object is of correct type2. Confirm arguments are of correct type3. Look up method on Java class4. Cache method5. Invoke method

invokeinterface1. Confirm object’s type implements interface2. Confirm arguments are of correct type3. Look up method on Java class4. Cache method5. Invoke method

invokespecial1. Confirm object is of correct type2. Confirm arguments are of correct type3. Confirm target method is visible4. Look up method on Java class5. Cache method6. Invoke method

invokedynamic1. Call your language's logic2. Install target function3. Target function invoked directly until you change it

Page 28: JRuby and Invokedynamic - Japan JUG 2015

function pointers

invokedynamic

language logic

target method

JVM

Page 29: JRuby and Invokedynamic - Japan JUG 2015

method handles

call site

bootstrap

target method

JVM

Page 30: JRuby and Invokedynamic - Japan JUG 2015

Method Handles

Page 31: JRuby and Invokedynamic - Japan JUG 2015

Method Handles

• Function/field/array pointers

• Argument manipulation

• Flow control

• Optimizable by the JVM

• This is very important

Page 32: JRuby and Invokedynamic - Japan JUG 2015

java.lang.invoke

•MethodHandles

• Utilities for acquiring, adapting handles

•MethodType

• Representation of args + return type

•MethodHandle

• An invokable target + adaptations

Page 33: JRuby and Invokedynamic - Japan JUG 2015

MethodHandles.Lookup

• Used to get function pointers

•MethodHandles.lookup()

• Obeys visibility of lookup() location

• Can expose private members

Page 34: JRuby and Invokedynamic - Japan JUG 2015

MethodHandle Targets

• Methods, constructors

• Fields

• Array elements

Page 35: JRuby and Invokedynamic - Japan JUG 2015

MethodHandles.Lookup

• Method pointers

•findStatic, findVirtual, findSpecial, findConstructor

• Field pointers

•findGetter, findSetter, findStaticGetter, findStaticSetter

Page 36: JRuby and Invokedynamic - Japan JUG 2015

// can access everything visible from hereMethodHandles.Lookup LOOKUP = MethodHandles.lookup();

// can access only public fields and methodsMethodHandles.Lookup PUBLOOKUP = MethodHandles.publicLookup();

Page 37: JRuby and Invokedynamic - Japan JUG 2015

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

Page 38: JRuby and Invokedynamic - Japan JUG 2015

// example JavaString value1 = System.getProperty("java.home"); // getProperty signatureMethodType type1 = MethodType.methodType(String.class, String.class); MethodHandle getPropertyMH = LOOKUP .findStatic(System.class, "getProperty", type1); // invokeString value2 = (String) getPropertyMH.invoke("java.home");

Page 39: JRuby and Invokedynamic - Japan JUG 2015

// example JavaSystem.out.println("Hello, world"); // println signatureMethodType type2 = MethodType.methodType(void.class, Object.class); MethodHandle printlnMH = LOOKUP .findVirtual(PrintStream.class, "println", type2); // invokeprintlnMH.invoke(System.out, (Object) "Hello, world");

Page 40: JRuby and Invokedynamic - Japan JUG 2015

Adapters

• java.lang.invoke.MethodHandles.*

• Argument manipulation, modification

• Flow control and exception handling

• Similar to writing your own command-pattern utility objects

Page 41: JRuby and Invokedynamic - Japan JUG 2015

Argument Juggling

• insert, drop, permute

• filter, fold

• splat (varargs), spread (unbox varargs)

Page 42: JRuby and Invokedynamic - Japan JUG 2015

// insert is like partial applicationMethodHandle getJavaHomeMH = MethodHandles.insertArguments(getPropertyMH, 0, "java.home"); // same as getProperty("java.home")getJavaHomeMH.invokeWithArguments();

Page 43: JRuby and Invokedynamic - Japan JUG 2015

MethodHandle systemOutPrintlnMH = MethodHandles.insertArguments(printlnMH, 0, System.out); // same as System.out.println(...systemOutPrintlnMH.invokeWithArguments("Hello, world");

Page 44: JRuby and Invokedynamic - Japan JUG 2015

Flow Control

• guardWithTest is a boolean branch

• Three targets

• Condition ("if")

• True path ("then")

• False path ("else")

Page 45: JRuby and Invokedynamic - Japan JUG 2015

// boolean branch // example Javaclass UpperDowner { public String call(String inputString) { if (randomBoolean()) { return inputString.toUpperCase(); } else { return inputString.toLowerCase(); } }}

Page 46: JRuby and Invokedynamic - Japan JUG 2015

// randomly return true or falseMethodHandle upOrDown = LOOKUP.findStatic( BasicHandles.class, "randomBoolean", methodType(boolean.class));

// guardWithTest calls boolean handle and branchesMethodHandle upperDowner = guardWithTest( upOrDown, toUpperCaseMH, toLowerCaseMH);

upperDowner.invoke("Hello, world"); // HELLO, WORLDupperDowner.invoke("Hello, world"); // hello, worldupperDowner.invoke("Hello, world"); // HELLO, WORLDupperDowner.invoke("Hello, world"); // HELLO, WORLDupperDowner.invoke("Hello, world"); // hello, world

Page 47: JRuby and Invokedynamic - Japan JUG 2015

Bootstrap

Page 48: JRuby and Invokedynamic - Japan JUG 2015

Bootstrap

• First time JVM sees invokedynamic

• Call your bootstrap code with name, type

• Install resulting CallSite

• Subsequent times

• Just invoke call site contents

Page 49: JRuby and Invokedynamic - Japan JUG 2015

CallSite

• Holds a MethodHandle

• Returned to JVM by bootstrap method

• Replaces invokedynamic bytecode

• JVM watches it for changes

Page 50: JRuby and Invokedynamic - Japan JUG 2015

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)); }

Page 51: JRuby and Invokedynamic - Japan JUG 2015

Mutable Call Sites

• Target can be changed

• Trivial example of late binding

Page 52: JRuby and Invokedynamic - Japan JUG 2015

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!"

Page 53: JRuby and Invokedynamic - Japan JUG 2015

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;}

Page 54: JRuby and Invokedynamic - Japan JUG 2015

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!");}

Page 55: JRuby and Invokedynamic - Japan JUG 2015

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!");}

Page 56: JRuby and Invokedynamic - Japan JUG 2015

All Together

• Bytecode gets call site

• Method handle points at target method

• Call site caches it

• Guard ensures it's the right method

Page 57: JRuby and Invokedynamic - Japan JUG 2015

Invokedynamic in JRuby

Page 58: JRuby and Invokedynamic - Japan JUG 2015

Dynamic Invocation

• The obvious one

• Method lookup based on runtime types

• Potentially mutable types

• Type check specific to language

Page 59: JRuby and Invokedynamic - Japan JUG 2015

class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display

Page 60: JRuby and Invokedynamic - Japan JUG 2015

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

foo bar bazJRubycalllogic

JRubycalllogic

Stops many JVM optimizations

JRuby before invokedynamic

JRubycalllogic

Page 61: JRuby and Invokedynamic - Japan JUG 2015

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

foo bar bazJRubycalllogic

JRubycalllogic

Dynamic call logic built into JVM

JRuby on Java 7

JRubycalllogicX X

Page 62: JRuby and Invokedynamic - Japan JUG 2015

Dynamic Invocation

TargetObject

Method Tabledef foo ...

def bar ...

associated with

obj.foo() JVM

def foo ...

Call Site

Page 63: JRuby and Invokedynamic - Japan JUG 2015

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

foo bar baz

Straight through dispatch path

JRuby on Java 7

Page 64: JRuby and Invokedynamic - Japan JUG 2015

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

foo bar baz

Optimizations (like inlining) can happen!

JRuby on Java 7

Page 65: JRuby and Invokedynamic - Japan JUG 2015

Empty Method Call

10M calls to empty method

0

1.25

2.5

3.75

5

Time in seconds

Page 66: JRuby and Invokedynamic - Japan JUG 2015

Empty Method Call

10M calls to empty method

0.32

0.328

0.335

0.343

0.35

Time in seconds

Page 67: JRuby and Invokedynamic - Japan JUG 2015

Lazy Constants

• Call site just produces a value

• Value calculated once

• Subsequent access is direct, optimizable

• Used for numbers, strings, regexp

Page 68: JRuby and Invokedynamic - Japan JUG 2015

class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display

Page 69: JRuby and Invokedynamic - Japan JUG 2015

Lazy Constants

Lazy ComputationLAZY_CONST JVM

Call Site

value

Page 70: JRuby and Invokedynamic - Japan JUG 2015

Ruby Constants

• Ruby's constants can be modified

• ...but it is very rare

• Look up value

• Guard against it changing

Page 71: JRuby and Invokedynamic - Japan JUG 2015

class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display

Page 72: JRuby and Invokedynamic - Japan JUG 2015

Constant Lookup

Look up "Foo" constant 10m times

0

0.3

0.6

0.9

1.2

Time in seconds

MRI JRuby Control

Page 73: JRuby and Invokedynamic - Japan JUG 2015

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

Page 74: JRuby and Invokedynamic - Japan JUG 2015

class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display

Page 75: JRuby and Invokedynamic - Japan JUG 2015

MyObject@foo@bar@baz

puts @foo

Page 76: JRuby and Invokedynamic - Japan JUG 2015

MyObject@foo@bar@baz

puts @foo

Offset:1

Page 77: JRuby and Invokedynamic - Japan JUG 2015

MyObject@foo@bar@baz

puts @foo

Offset:1

Page 78: JRuby and Invokedynamic - Japan JUG 2015

Instance Variables

Look up @foo variable 10M times

0

0.15

0.3

0.45

0.6

Time in seconds

MRI JRuby Control

Page 79: JRuby and Invokedynamic - Japan JUG 2015

class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display

Page 80: JRuby and Invokedynamic - Japan JUG 2015

Real World

Page 81: JRuby and Invokedynamic - Japan JUG 2015

bench_fractal bench_flipflop_fractal

• Mandelbrot generator

• Integer loops

• Floating-point math

• Julia generator using flip-flops

• I don’t really understand it.

Page 82: JRuby and Invokedynamic - Japan JUG 2015
Page 83: JRuby and Invokedynamic - Japan JUG 2015

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

Page 84: JRuby and Invokedynamic - Japan JUG 2015
Page 85: JRuby and Invokedynamic - Japan JUG 2015

bench_flipflop_fractal

0s

0.375s

0.75s

1.125s

1.5s

Category Title

0 1 2 3 4 5 6 7 8 9

Ruby 2.2.2 JRuby + indy

Page 86: JRuby and Invokedynamic - Japan JUG 2015

Times Faster than Ruby 2.2.2

0

1

2

3

4

base64 richards neural

3.66

3.44

2.658

1.914

1.5381.346

JRuby/Java 6 JRuby/Java 7

Page 87: JRuby and Invokedynamic - Japan JUG 2015

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

Page 88: JRuby and Invokedynamic - Japan JUG 2015

Future Work

• More creative use of invokedynamic in JRuby

• Smarter compiler above JVM byte code

• Continued JVM optimization

Page 89: JRuby and Invokedynamic - Japan JUG 2015

Thank You!

• Charles Oliver Nutter

• @headius

[email protected]

• https://github.com/jruby/jruby