Traditional languages compiling to Java bytecode
If you're using a language like Scala, Kotlin, Groovy, or Ceylon, you don't need the GraalVM to write a multilingual application. These languages compile to Java bytecode, so there's a common denominator. In theory, nothing stops a - say - Scala class from calling a Java method or a Groovy method. This approach works well. Currently, I'm using a mixed setup of Java 11 and Kotlin at work.World of languages. Image published at freesvg.org under a CC0 license by j4p4nThere's just one little disadvantage. Every language has it's own data types. As long as you stick to the least common denominator, everything's fine, but more often than not, you have to convert an advanced data type of your pet language to a bread-and-butter Java type.
Non-JVM languages aren't first-class citizens of Graal country
- High-level introduction to the GraalVM series
- Low-level stuff: bytecode, interpreters, and compilers
- Optimization strategies of the GraalVM
- Tree rewriting: how to implement an optimizer?
- Hands-on experience with GraalVM 2019.3. Is it ready yet?
- Polyglot programming. Including JS and Ruby benchmarks.
- Multilingual programming: using the best of many worlds
- Truffle - Graal's compiler-compiler. Polyglot programming under the hood.
- Unleashing the power of native cloud computing
ruby myProgram.rb. There's no difference in using one of the traditional Ruby interpreters.
build.gradle that links source codes from the two languages. At the end of the day, both Java and Kotlin files compile to bytecode, so calling one from the other is easy.
What polyglot programming looks and feels like
String and pass that
String to Truffle. That's the same approach we already know from Nashorn and Rhino. Rhino is probably the better comparison because the API of Truffle resembles the API of Rhino.
Update March 07, 2021:
The disadvantage is there's no
After some investigation, I finally found out that it's possible to use
Now the variable
What about performance?
We converted this example into a proper benchmark, pretty much the same way as we did in the previous part of this series. You can consult the source code at GitHub. The results were encouraging.
Update March 07, 2021:
GraalVM CE 21.0.0 runs the demo a few percent faster. In particular, the start-up penalty has become a lot better.
A string-oriented benchmark
Of course, that's a far cry from being a scientific benchmark, but it has several interesting traits:
- it deals massively with strings
- it keeps the garbage collector busy
- it uses non-linear code execution (recursive-descent parsing).
Like always, we've published our benchmark on GitHub, so you run the benchmark yourself.
On GraalVM 20.1.0, Truffle is the fastest engine, followed by Nashorn and Rhino. To our surprise, it's even faster than native node.js 12.4.1, at least in the long run. There's a severe cold start penalty. It takes GraalVM more than a hundred iterations to overtake node.js. The additional overhead of the Futamura projection shows.
|#1||2276 ms||4170 ms||1595 ms||177 ms|
|#2||872 ms||1220 ms||803 ms||89 ms|
|#10||398 ms||238 ms||403 ms||59 ms|
|#20||161 ms||144 ms||366 ms||54 ms|
|#50||115 ms||107 ms||344 ms||63 ms|
|#100||63 ms||101 ms||358 ms||50 ms|
|#150||44 ms||100 ms||344 ms||50 ms|
|#200||43 ms||99 ms||340 ms||65 ms|
|#250||42 ms||101 ms||344 ms||65 ms|
|#300||43 ms||142 ms||354 ms||48 ms|
Did I sell Truffle successfully to you? Well, I just compared the performance of the engines on the GraalVM. In the real world, you're probably using one of the good old standard JDKs. So I ran the benchmark again with AdoptOpenJDK 11.0.5. Of course, there's not Truffle. But in the long run, Nashorn almost reaches the performance of both node.js and Truffle.
|#1||n/a||5194 ms||1365 ms||177 ms|
|#2||n/a||1601 ms||650 ms||89 ms|
|#10||n/a||285 ms||446 ms||59 ms|
|#20||n/a||216 ms||390 ms||54 ms|
|#50||n/a||71 ms||400 ms||63 ms|
|#100||n/a||118 ms||386 ms||50 ms|
|#150||n/a||64 ms||400 ms||50 ms|
|#200||n/a||64 ms||388 ms||65 ms|
|#250||n/a||86 ms||375 ms||65 ms|
|#300||n/a||65 ms||375 ms||48 ms|
We've also run the benchmark with AdoptOpenJDK 14.0.5. For some reason, Java 14 seems to be slower than Java 11. Maybe the LTS versions are heavily optimized, while development on the other version focuses on features. It's not the first time we've observed the performance penalty of Java 14. Newer is not always better. Putting it in IT terms: newer is not always faster.
Doing it the other way round
Come to think of it, that's no surprise except that we'd expect that from a virtual machine that claims to be polyglot. When we heard about it, we thought of a virtual machine that can use any combination of language, each interoperating with every other language.
As it turns out, we're using Truffle, the framework powering the polyglot features, to call methods in foreign languages. Truffle runs a wide variety of programming languages, but there's a huge class of languages not run by Truffle: the languages compiling to bytecode, such as Kotlin, Scala, Ceylon, Groovy, and Java itself.
Update March 07, 2021:
Recently Espresso made in into the official GraalVM release train. Espresso is a Java bytecode interpreter written with Truffle. So if you're ready to sacrifice some performance, you can now easily call Java code from every other language supported by Truffle. Currently, the Espresso team is working hard on improving the performance of Espresso, but it's unlikely it'll ever match the performance of the native Java implementation. On the other hand, if the primary language of your application is not Java, the performance of Espresso isn't important. Espresso opens opportunities you didn't have without it.
Cutting a long story short: Truffle enables a remarkable amount of interoperability, but it's not perfect. Java (run the tradional way, i.e. without Espresso) and the JVM languages get a special treatment in Truffle. Any Java object you want to use must be passed to Truffle. To do so, just call the method
putMember() of the Truffle
Other programming languages
The nice thing about it is that not only Java can call code written in any of these languages, but these languages can also call each other. You Ruby program can call R, which calls a C function. Nice!
Limits of interoperability
Wrapping it up
About the co-authorKarine Vardanyan occupies herself with making her master at the technical university Darmstadt, Germany. Until recently, she used to work at OPITZ CONSULTING, where she met Stephan. She's interested in artificial intelligence, chatbots, and all things Java.
Sourceclass of the GraalVM. Other than that, there's little to complain. The API is simple and does the trick.
We've even seen a decent performance in our tests. Not perfect, but we're sure future GraalVM versions have a lot in store for us. So what can you do with the polyglot approach?
Among other things, you can use Truffle to store algorithms in the database. Please proceed extremely carefully, because that's the hacker's dream. Nonetheless, almost every application can be configured. Configuration is the key to success. Being able to configure algorithms may give your company an extra sales boost.
Another benefit is you can use algorithms that happen to be written in the wrong language. Now you don't have to migrate them. You can use LLVM to cross-compile them and run them using Truffle.
We're curious how people are going to use this feature to good use!
It goes without saying we're less curious to see how evil hackers are going to use this feature, so be careful if you're exposing your application to algorithms stored in the database. It's a two-sided sword.
Our number-crunching benchmark on GitHub