Help

Experiments with GraalVM native images (1/2)

Previous Article

This blog post is about the experience I made when compiling the GraphHopper routing engine to a native executable using GraalVM.

The GraphHopper routing engine is an open source project that uses the road network from OpenStreetMap and is able to calculate routes from A to B very fast and offers other features like isochrone calculation or map matching.

The GraalVM project is a huge project from Oracle Labs, see its official website. The technology behind “GraalVM native images” allows you to create a standalone native executable e.g. for Linux. This means you can run Java code without a JVM.

For our purpose we do the following steps:

  • install GraalVM with native-image
  • create some config files that tells GraalVM to include certain resources or how to deal with reflection etc.
  • the Java code including a C hook
  • the C++ code that calls the C hook of the Java code

To get started download GraalVM for your OS e.g. GraalVM CE for Linux amd64 and unzip it. Let JAVA_HOME point to this location. Then install the native-image tool into this installation:
$JAVA_HOME/bin/gu install native-image
This tool needs a local C toolchain: glibc-devel, zlib-devel and gcc. On Linux you can install this via:
sudo apt-get install zlib1g-dev
You can now verify that the native-image tool works:
$JAVA_HOME/bin/native-image --help

To create the config files automatically you call:

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=./ni-configs -jar target/*-jar-with-dependencies.jar

When you include native-image-maven-plugin in your maven build you add these config files via:

-H:ConfigurationFileDirectories=${project.basedir}/ni-configs

See the full pom.xml here that also adds the dependency “library-support” so that we can include the static C hook to our Java code:

@CEntryPoint(name = "runGH")
public static double runGH(IsolateThread thread, /*more params*/) {
  return internalRun(/*more params*/);
}

Which we then can call in our C++ code:

#include <iostream>
#include "libgraphhopper.h"

using namespace std;

int main(int argc, char **argv) {
// This is some Graal boilerplate code
graal_isolatethread_t *thread = NULL;
if (graal_create_isolate(NULL, NULL, &thread) != 0) {
  fprintf(stderr, "graal_create_isolate error\n");
  return 1;
}
...
double distance = runGH(thread, lat1, lon1, lat2, lon2);
std::cout << "Distance calculated by GraphHopper " << distance << std::endl;

// Clean up Graal stuff
if (graal_detach_thread(thread) != 0) {
  fprintf(stderr, "graal_detach_thread error\n");
  return 1;
}
}

We can now build the project via
mvn clean package
that creates an executable at ./target/graphhopper which is 15MB.

If you put some OpenStreetMap data at osm.pbf you can already start it and the second time you run it the startup performance is really nice. For OpenJDK it takes approx. 500ms which is already very good as you have to keep in mind that the road data is already loaded via memory mapping, but with the native image tool we can start GraphHopper, load the graph and run a routing request in under 30ms! For production it is likely faster as I just tested this on my weak laptop.

Low Startup Time with GraalVM native images

So what did we just do? We created a Java program which was started without a JVM!? That is the magic of GraalVM native images. It can handle other JVM-based languages like Scala, Clojure and Kotlin too. The resulting native image can, optionally, execute dynamic languages like JavaScript, Python etc. For more details visit the official documentation.

Of course this has some downsides like lower peak performance and longer compilation, but it makes Java now a viable choice to be used from within a bash loop or in a so called “serverless” scenario.

In the next post you’ll learn how we can use GraalVM to run GraphHopper native on Android.