top | item 2928285

Why Clojure doesn't need invokedynamic

148 points| apgwoz | 14 years ago |groups.google.com | reply

53 comments

order
[+] headius|14 years ago|reply
I'll do a bulk reply since there's lots of comments here I would reply to.

Clojure doesn't need invokedynamic in general because Clojure doesn't really dispatch dynamically. It's dynamically typed, but calls are made via a known path to a specific function, and only via rebinding can that target change. The general case goes straight through.

In a sense, it's like Clojure is JRuby where all classes only have one method. With only one method, you can always just call straight in, no lookup required, and avoid dynamic lookup.

If Clojure had to deal with dynamically selecting a method based on a target object type, incoming parameter types, and so on, invokedynamic would be more useful. Indeed, Clojure often hacks around such cases by allowing you to specify Java signatures (to avoid reflection) or primitive types (to avoid boxed math). Both cases could be improved without ugly syntax if invokedynamic were used.

So to summarize, Clojure could get benefit out of invokedynamic...if it hadn't already added hacks and syntax to allow opting out of the most-dynamic cases.

In JRuby, we could avoid a lot of dynamic dispatch by allowing type signatures, type declarations, and so on. But that wouldn't be Ruby, and we don't control Ruby. JRuby compares favorably to Clojure without the typing syntax, performance-wise, which is quite acceptable to me. With invokedynamic, JRuby even compares favorably to Java, with the exception of boxed math (which is very hard to optimize in a dynamic language). For equivalent work, JRuby with invokedynamic is within 2x slower than Java, and that will continue to improve as the Hotspot guys optimize invokedynamic.

[+] fogus|14 years ago|reply
Aiyayyay. Were to begin.

    calls are made via a known path to a specific 
    function, and only via rebinding can that target 
    change. The general case goes straight through.
This is half true. The rebinding via def will change the target, but the call always happens via a lookup through a volatile. In fact, it's this very chain that can benefit from invokedynamic.

    If Clojure had to deal with dynamically 
    selecting a method based on a target object 
    type
Clojure's protocols are polymorphic on the type of the first argument. The protocol functions are call-site cached (with no per-call lookup cost if the target class remains stable). This is another place where invokedynamic would help.

    benefit out of invokedynamic...if it hadn't 
    already added hacks and syntax
I think you're mixed up here. The JVM already optimizes classes and interfaces. All that Clojure's type hints allow you to do is to help the compiler in the cases where it's unable to infer the proper type. Hinting is not always necessary and better inferencing will start chipping into the remaining cases.

    JRuby compares favorably to Clojure without 
    the typing syntax, performance-wise, which 
    is quite acceptable to me. With invokedynamic
That's because JRuby is awesome tech. You've certainly set the bar high for dynamic dispatch that invokedynamic has yet to meet.

    as the Hotspot guys optimize invokedynamic.
What's the over-under on it happening by Java8? Java9?
[+] swannodette|14 years ago|reply
While I respect your work on JRuby, I think you're not being honest about JRuby performance here nor being fair about Clojure's design decisions - they are not hacks.

Clojure supports unboxed math. One big example to why this important - it's possible to implement Clojure's persistent data structures (and new ones) in Clojure itself. They are the very cornerstone of what Clojure is all about and this is a case where unboxed performance makes all the difference in the world.

Persistent data structures are not that relevant to JRuby and thus you don't need to make that design decision.

[+] SeanLuke|14 years ago|reply
>> I work a lot with Rhino, JRuby, Jython, Groovy and Quercus (a JVM PHP engine), and can anecdotally say that Clojure and Quercus are "fastest" of their breed for the kind of web development work I do.

Interesting. We maintain a Java-based simulation toolkit in which the target language (Clojure, whatnot) must make a lot of calls to Java and work with a lot of mutable Java data. And it's been our experience that Clojure is -- I do not make up this number -- approximately 1000 times slower than Kawa for such tasks. Mostly because of immutability guarantees and Refs. Indeed, it's one of the slowest languages on the block for this task.

I wonder how Kawa would perform in his web development arena. I've found it the fastest non-Java JVM language anywhere.

[+] arohner|14 years ago|reply
In my experience (using Clojure full time since before there were official releases), Clojure can be written exactly as fast as java. That isn't idiomatic clojure code however.

The features clojure provides are somewhat slower, but well worth it. The purely functional datastructures are ~25% slower than their java equivalents. The seq abstraction is slightly slower, but allows handling infinite sequences, and only passes over the data once.

About the only thing I'm aware of that can slow your code down that much is by using reflection in a tight loop.

Hop on to #clojure on irc.freenode.net, we'd love to help you out.

[+] fogus|14 years ago|reply
I'm actually not able to follow your usage scenario. Do you mind providing a representational bit of code to illustrate?
[+] andrewcooke|14 years ago|reply
are you making many small calls, each of which modifies a small part of a large structure, and clojure is creating a new instance of the large structure each time? that sounds like a pathological case for idiomatic clojure, but i would have thought you could work round it by keeping the data in a more "java like" format without too much pain.

i've been using clojure for numerical processing this last few weeks and have been impressed how well it mixes lazy sequences and vectors. but i haven't had to face a case like you seem to be having.

[+] wgren|14 years ago|reply
>"So, when Clojure calls a function, it either already has the instance in its entirety (a lambda) or it finds it by dereferencing a binding. Since all functions are instances that implement IFn, the implementation can then call invokeinterface, which is very efficient. "

Perhaps, but from my understanding with InvokeDynamic, for a function that ends up with for instance adding two numbers, we can guarantee the types even though the code is dynamic. This means the JVM can perform optimizations like inlining. This invokeinterface call could then be replaced with an "iadd" bytecode at the call site, which in turn can get JITed, and so on.

[+] justinsb|14 years ago|reply
I'd like to know more about this. My understanding is that every method in Clojure has this sort of signature: Object F(Object, Object,.. Object)

So Add looks like this: Object Add(Object, Object)

Now if Clojure recognizes that this is often called with longs, then it would make sense to produce what is C++ would be a template specialization: long Add(long, long). As you pointed out, that could then be inlined.

As long as we can specialize all the way up the call chain, we don't need any JVM magic. But that would be a lucky case, and I believe invokedynamic can help with our problem case: an argument-dependent transition from non-specialized code to specialized code. i.e., call Add(long, long) iff both args are Longs, otherwise call Add(Object, Object).

So, Clojure would profit from invokedynamic if it (transparently) introduced specialized methods for optimization. Of course, this is not exactly a trivial optimization to implement - it may be that the most practical way to implement it is to look at the runtime behaviour (sort of like what the JIT compiler does). Ideally the JVM would be able to do all this magic specialization for us (given the parallels to JIT compilation), but I doubt that it can do that at present.

Anyone know if I'm off the mark here, or whether Clojure would benefit if it did what I've termed "specialization"?

[+] wccrawford|14 years ago|reply
I love logic.

However, when dealing with optimizations, actual experimentation is a lot more convincing. I've seen far too many times when things 'should have been' better a certain way, but weren't... For various reasons.

[+] fogus|14 years ago|reply
Logic isn't even required in this case (although I suppose it can never hurt). Charles Nutter has shared some numbers for JRuby constant lookups at http://blog.headius.com/2011/08/invokedynamic-in-jruby-const...

- JRuby 2

- JRuby with current invokedynamic 35

- JRuby with some dev build that some guy who knows a guy who knows a guy at Oracle gave him: 0.5 [1]

The future might look bright, but a problem with the future is that it comes when it's ready and never before, no matter how much we want it to or might need it.

[1]: I'm just being playful here. I actually have no idea where Charles got that dev build.

[+] mattdeboard|14 years ago|reply
This is why I'm in college. I want to be able to understand this sort of thing better.
[+] Jach|14 years ago|reply
At what college are they teaching this stuff? Are there schools that discuss the JVM environment outside of just using it for Java?
[+] Stasyan|14 years ago|reply
You don't have to be in college to understand that. Internet has more than enough resources for you to learn.
[+] fogus|14 years ago|reply
This is a nice writeup, but it seems to miss some important details. Clojure may not need invokedynamic in its current state, but it could certainly benefit should many of its limitations dissipate.
[+] nickik|14 years ago|reply
Could you get into some more detail on what we would win? The author mentitiond static methods als clojure functions. What else?
[+] mypov|14 years ago|reply
Just curious why a single class with a single method taking object array would not solve the issue? Granted the dynamic marshall would be a perf hit. Other than that?
[+] guard-of-terra|14 years ago|reply
When I wrote some cpu-bound code in Clojure which also used several java classes and libraries, I've found that the naive implementation 5x as slow as Rhino implementation, but when I've added some type hints (which I found out by enabling warn-on-reflection), code became ten times as fast, beating rhino twofold.

I wonder if it's possible to use invokedynamic when there is an unhinted java interop to avoid drastic slowdown that I've experienced. Because it's obvious that many necessary type hints would be absent in a big real-world program.

[+] swannodette|14 years ago|reply
In a big real world Clojure program you would probably control the amount of interop, or abstract it away. Lots of type hints is not indicative of a well-written Clojure program of any size, a good example of how this can be done over JDK 7 ForkJoin, https://gist.github.com/888733#file_forkjoin.clj. Note the code at the bottom has no type hints yet will be nearly as fast as if you littered code with such noise - this is because of the lovely thing that is JVM inlining.