cli4clj 1.7.2 – “GraalVM-ready”

In this post, I briefly summarize my experience of making cli4clj “GraalVM-ready”.

Inspired by Jan Stepien’s talk about GraalVM at :clojureD 2019, I got curious about GraalVM. So, I gave it a try to build a “native” executable of the cli4clj example.

The part of GraalVM that interested me, right now, was the capability to build native executables for JVM-based software. However, this post is not about introducing GraalVM. For more information about GraalVM, see, e.g., the GraalVM website or Jan’s blog post on Clojure with the GraalVM.

For building the native image, I first created a standalone Jar via “lein uberjar”. Afterwards, I used GraalVM’s native-image to build the native image:

~/r/p/c/cli4clj ((ec7ba86a…)) ~/Downloads/graalvm-ce-1.0.0-rc12/bin/native-image --no-server --report-unsupported-elements-at-runtime -jar target/cli4clj-1.7.2-standalone.jar 
[cli4clj-1.7.2-standalone:17220]    classlist:   5,198.22 ms
[cli4clj-1.7.2-standalone:17220]        (cap):   1,395.43 ms
[cli4clj-1.7.2-standalone:17220]        setup:   3,496.17 ms
[cli4clj-1.7.2-standalone:17220]   (typeflow): 335,004.07 ms
[cli4clj-1.7.2-standalone:17220]    (objects):  31,205.97 ms
[cli4clj-1.7.2-standalone:17220]   (features):     746.55 ms
[cli4clj-1.7.2-standalone:17220]     analysis: 368,574.15 ms
[cli4clj-1.7.2-standalone:17220]     universe:   7,927.50 ms
[cli4clj-1.7.2-standalone:17220]      (parse):   9,650.90 ms
[cli4clj-1.7.2-standalone:17220]     (inline):   5,801.23 ms
[cli4clj-1.7.2-standalone:17220]    (compile):  50,067.12 ms
[cli4clj-1.7.2-standalone:17220]      compile:  67,484.15 ms
[cli4clj-1.7.2-standalone:17220]        image:   4,716.99 ms
[cli4clj-1.7.2-standalone:17220]        write:     552.31 ms
[cli4clj-1.7.2-standalone:17220]      [total]: 458,269.09 ms

However, to make this work, I had to do some adjustments to cli4clj to “make GraalVM happy”:

  • Add type hints to avoid reflective calls.
  • Downgrade Clojure from 1.10 to 1.9 as, currently, Clojure 1.10 does not work with GraalVM.
  • Add a customized version of clojure.main/skip-whitespace, which adds type hints and removes the “readLine” case.

Furthermore, there is still a minor lurking issue. The verbose exception printing, which is disabled by default, will not work yet as clojure.stacktrace/print-cause-trace is not “GraalVM friendly” yet. I did not address this aspect yet as it only concerns a minor use case and I wanted to experiment a bit first. In future, if GraalVM support proofs valuable, I will try to fix this.

As a result of the native image generation, I got an ~37 MB executable of the cli4clj example. Note that I used dynamic linking on Linux, which caused the following shared object dependencies:

~/r/p/c/c/dist (master=) ldd cli4clj-1.7.2-standalone
	linux-vdso.so.1 (0x00007ffdc9fc6000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f31d15d2000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007f31d15cc000)
	libz.so.1 => /lib64/libz.so.1 (0x00007f31d15b2000)
	libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007f31d1577000)
	librt.so.1 => /lib64/librt.so.1 (0x00007f31d156d000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f31d13a7000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f31d161d000) 

So far, I could run the generated executable on Fedora and in Windows with the Windows Subsystem for Linux using a Debian basis.

Below is a simple comparison of running the native executable produced by GraalVM versus the standalone Jar run via Java:

r/p/c/c/dist (master=) time ./cli4clj-1.7.2-standalone
cli# q
0.03user 0.06system 0:03.38elapsed 2%CPU (0avgtext+0avgdata 50488maxresident)k
0inputs+8outputs (0major+9253minor)pagefaults 0swaps

~/r/p/c/c/dist (master=) time java -jar cli4clj-1.7.2-standalone.jar 
cli# q
6.33user 0.20system 0:06.27elapsed 104%CPU (0avgtext+0avgdata 183840maxresident)k
96inputs+8outputs (0major+45981minor)pagefaults 0swaps

Note that the reported system time is rather meaningless as the cli4clj example does not automatically terminate but waits at the command input prompt. So, the reported system time mainly depends on how quickly I pressed “q” for terminating the example application.

The other reported values are not impacted that notably by this peculiarity.

The start-up time of the native version is notably faster, which can also be observed in the reported user time and CPU usage. Furthermore, the memory footprint of the native version is less than a third of the Jar file counterpart.

I uploaded a native build of the cli4clj example application and the corresponding standalone Jar to the cli4clj repository.

Furthermore, now (starting with version 1.7.2), you can include cli4clj in your GraalVM-based command line applications.

I hope you find this blog post and cli4clj useful. If you have constructive feedback please let me know.

Advertisements
This entry was posted in cli4clj, Libs. and tagged , , , , . Bookmark the permalink.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.