Upload
charles-nutter
View
606
Download
0
Embed Size (px)
Citation preview
❤ INVOKEDYNAMIC
Me
• Charles Oliver Nutter
• @headius, [email protected]
• JVM language developer at Red Hat
• JRuby co-lead since 2005
Agenda
• Brief intro to JRuby (and Ruby)
• Overview of invokedynamic
• Invokedynamic examples
• JRuby + invokedynamic
+
2001: JRuby is Born
2005: JRuby on Rails
2015: JRuby 9000
Ruby
• Created in 1995 by Yukihiro Matsumoto
• Dynamically typed (dynamic calls)
• Object-oriented
• Everything is an object
• Tight integration with C, UNIX
Ruby
• Created in 1995 by Yukihiro Matsumoto
• Dynamically typed (dynamic calls)
• Object-oriented
• Everything is an object
• Tight integration with C, UNIX
class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display
Ruby == Method CallsThousands upon thousands of them.
Command Number of simple calls
jruby -e 1 1k
gem install rails 315k
rails new testapp 606k
rails simple CRUD 16k
def foo bar end def bar baz end def baz # ... end
foo bazbar
def foo bar end def bar baz end def baz # ... end
foo bar bazJRubycalllogic
JRubycalllogic
Stops many JVM optimizations
JRuby before invokedynamic
JRubycalllogic
Invocation
TargetObject
Object’sClassvoid foo()
static void bar()
instanceof
obj.foo() JRuby
void foo()
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); }}
def foo bar end def bar baz end def baz # ... end
foo bar bazJRubycalllogic
JRubycalllogic
Stops many JVM optimizations
JRuby before invokedynamic
JRubycalllogic
Invokedynamic
"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
Goals of InvokeDynamic
• A new bytecode
• Fast function pointers + adapters
• Caching and invalidation
• Flexible enough for future uses
How does it work?
Invoke
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)
// 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
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
function pointers
invokedynamic
language logic
target method
JVM
method handles
call site
bootstrap
target method
JVM
Method Handles
Method Handles
• Function/field/array pointers
• Argument manipulation
• Flow control
• Optimizable by the JVM
• This is very important
java.lang.invoke
•MethodHandles
• Utilities for acquiring, adapting handles
•MethodType
• Representation of args + return type
•MethodHandle
• An invokable target + adaptations
MethodHandles.Lookup
• Used to get function pointers
•MethodHandles.lookup()
• Obeys visibility of lookup() location
• Can expose private members
MethodHandle Targets
• Methods, constructors
• Fields
• Array elements
MethodHandles.Lookup
• Method pointers
•findStatic, findVirtual, findSpecial, findConstructor
• Field pointers
•findGetter, findSetter, findStaticGetter, findStaticSetter
// can access everything visible from hereMethodHandles.Lookup LOOKUP = MethodHandles.lookup();
// can access only public fields and methodsMethodHandles.Lookup PUBLOOKUP = MethodHandles.publicLookup();
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
// 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");
// 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");
Adapters
• java.lang.invoke.MethodHandles.*
• Argument manipulation, modification
• Flow control and exception handling
• Similar to writing your own command-pattern utility objects
Argument Juggling
• insert, drop, permute
• filter, fold
• splat (varargs), spread (unbox varargs)
// insert is like partial applicationMethodHandle getJavaHomeMH = MethodHandles.insertArguments(getPropertyMH, 0, "java.home"); // same as getProperty("java.home")getJavaHomeMH.invokeWithArguments();
MethodHandle systemOutPrintlnMH = MethodHandles.insertArguments(printlnMH, 0, System.out); // same as System.out.println(...systemOutPrintlnMH.invokeWithArguments("Hello, world");
Flow Control
• guardWithTest is a boolean branch
• Three targets
• Condition ("if")
• True path ("then")
• False path ("else")
// boolean branch // example Javaclass UpperDowner { public String call(String inputString) { if (randomBoolean()) { return inputString.toUpperCase(); } else { return inputString.toLowerCase(); } }}
// 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
Bootstrap
Bootstrap
• First time JVM sees invokedynamic
• Call your bootstrap code with name, type
• Install resulting CallSite
• Subsequent times
• Just invoke call site contents
CallSite
• Holds a MethodHandle
• Returned to JVM by bootstrap method
• Replaces invokedynamic bytecode
• JVM watches it for changes
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)); }
Mutable Call Sites
• Target can be changed
• Trivial example of late binding
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!"
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;}
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!");}
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!");}
All Together
• Bytecode gets call site
• Method handle points at target method
• Call site caches it
• Guard ensures it's the right method
Invokedynamic in JRuby
Dynamic Invocation
• The obvious one
• Method lookup based on runtime types
• Potentially mutable types
• Type check specific to language
class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display
def foo bar end def bar baz end def baz # ... end
foo bar bazJRubycalllogic
JRubycalllogic
Stops many JVM optimizations
JRuby before invokedynamic
JRubycalllogic
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
Dynamic Invocation
TargetObject
Method Tabledef foo ...
def bar ...
associated with
obj.foo() JVM
def foo ...
Call Site
def foo bar end def bar baz end def baz # ... end
foo bar baz
Straight through dispatch path
JRuby on Java 7
def foo bar end def bar baz end def baz # ... end
foo bar baz
Optimizations (like inlining) can happen!
JRuby on Java 7
Empty Method Call
10M calls to empty method
0
1.25
2.5
3.75
5
Time in seconds
Empty Method Call
10M calls to empty method
0.32
0.328
0.335
0.343
0.35
Time in seconds
Lazy Constants
• Call site just produces a value
• Value calculated once
• Subsequent access is direct, optimizable
• Used for numbers, strings, regexp
class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display
Lazy Constants
Lazy ComputationLAZY_CONST JVM
Call Site
value
Ruby Constants
• Ruby's constants can be modified
• ...but it is very rare
• Look up value
• Guard against it changing
class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display
Constant Lookup
Look up "Foo" constant 10m times
0
0.3
0.6
0.9
1.2
Time in seconds
MRI JRuby Control
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
class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display
MyObject@foo@bar@baz
puts @foo
MyObject@foo@bar@baz
puts @foo
Offset:1
MyObject@foo@bar@baz
puts @foo
Offset:1
Instance Variables
Look up @foo variable 10M times
0
0.15
0.3
0.45
0.6
Time in seconds
MRI JRuby Control
class Hello def initialize(name) @name = name end def display puts "Hello, #{@name}" endendhello = Hello.newhello.display
Real World
bench_fractal bench_flipflop_fractal
• Mandelbrot generator
• Integer loops
• Floating-point math
• Julia generator using flip-flops
• I don’t really understand it.
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
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
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
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
Future Work
• More creative use of invokedynamic in JRuby
• Smarter compiler above JVM byte code
• Continued JVM optimization
Thank You!
• Charles Oliver Nutter
• @headius
• https://github.com/jruby/jruby