graalvm

Discuss GraalVM related topics. Use clojure 1.10.2 or newer for all new projects. Contribute to https://github.com/clj-easy/graal-docs and https://github.com/BrunoBonacci/graalvm-clojure. GraalVM slack: https://www.graalvm.org/slack-invitation/.
Hukka 2021-06-07T09:30:44.035600Z

I've been trying to get graalvm compilation working, but constantly hit a problem where at runtime the binary cannot find java.math.BigDecimal class. Tried googling that, but don't see anyone having the same problem. Is this a known limitation, or am I doing something wrong? I've tried using the clj.native-image package, but also building an uberjar with depstar and then calling native-image manually with native-image --no-fallback --initialize-at-build-time --no-server -jar hello.jar

borkdude 2021-06-07T09:32:11.035900Z

@tomi.hukkalainen_slac Can you give the specific error? It might be related to reflection

Hukka 2021-06-07T09:32:14.036Z

I can get basic stuff working, but the exception is thrown when I try to call clojure.core/bigdec

borkdude 2021-06-07T09:32:43.036200Z

In bb it works:

$ bb -e '(bigdec 1)'
1M

Hukka 2021-06-07T09:32:50.036400Z

** ERROR: **
Exception: #error {
 :cause java.math.BigDecimal
 :via
 [{:type java.lang.ClassNotFoundException
   :message java.math.BigDecimal
   :at [com.oracle.svm.core.hub.ClassForNameSupport forName ClassForNameSupport.java 64]}]
 :trace
 [[com.oracle.svm.core.hub.ClassForNameSupport forName ClassForNameSupport.java 64]
  [java.lang.Class forName DynamicHub.java 1308]
  [clojure.lang.RT classForName RT.java 2212]
  [clojure.lang.RT classForName RT.java 2221]
  [clojure.core$bigdec invokeStatic core.clj 3640]
  [clojure.core$bigdec invoke core.clj 3635]
  [hello$heka_with_cost invokeStatic hello.clj 91]
  [hello$heka_with_cost invoke hello.clj 85]
  [clojure.core$comp$fn__5825 invoke core.clj 2573]
  [clojure.core$map$fn__5884 invoke core.clj 2759]
  [clojure.lang.LazySeq sval LazySeq.java 42]
  [clojure.lang.LazySeq seq LazySeq.java 51]
  [clojure.lang.RT seq RT.java 535]
  [clojure.core$seq__5419 invokeStatic core.clj 139]
  [clojure.core.protocols$seq_reduce invokeStatic protocols.clj 24]
  [clojure.core.protocols$fn__8168 invokeStatic protocols.clj 75]
  [clojure.core.protocols$fn__8168 invoke protocols.clj 75]
  [clojure.core.protocols$fn__8110$G__8105__8123 invoke protocols.clj 13]
  [clojure.core$transduce invokeStatic core.clj 6886]
  [clojure.core$transduce invokeStatic core.clj 6881]
  [clojure.core$transduce invoke core.clj 6872]
  [hello$read_heka invokeStatic hello.clj 118]
  [hello$read_heka invoke hello.clj 115]
  [cli_matic.core$invoke_subcmd invokeStatic core.cljc 546]
  [cli_matic.core$invoke_subcmd invoke core.cljc 525]
  [cli_matic.core$run_cmd_STAR_ invokeStatic core.cljc 589]
  [cli_matic.core$run_cmd_STAR_ invoke core.cljc 560]
  [cli_matic.core$run_cmd invokeStatic core.cljc 601]
  [cli_matic.core$run_cmd invoke core.cljc 591]
  [hello$_main invokeStatic hello.clj 135]
  [hello$_main doInvoke hello.clj 134]
  [clojure.lang.RestFn applyTo RestFn.java 137]
  [hello main nil -1]]}

Hukka 2021-06-07T09:33:21.036600Z

Where the line 91 in my code is the call to bigdec

Hukka 2021-06-07T09:33:48.036800Z

Ah, sorry, I don't know where to reply ๐Ÿ™‚ Here?

borkdude 2021-06-07T09:34:47.037Z

ok fine

borkdude 2021-06-07T09:35:16.037200Z

I think the bigdec fun has a reflective usage in the last branch:

:else (BigDecimal. x)

borkdude 2021-06-07T09:35:26.037400Z

and you might be hitting that branch

borkdude 2021-06-07T09:35:33.037600Z

how are you creating that bigdec

Hukka 2021-06-07T09:37:05.037800Z

Threading a datum from clojure.data.csv/read-csv

borkdude 2021-06-07T09:37:27.038Z

You can likely solve this by incorporating java.math.BigDecimal in a reflection config

borkdude 2021-06-07T09:37:38.038200Z

it's probably using a string then

Hukka 2021-06-07T09:37:40.038400Z

So, from a string

Hukka 2021-06-07T09:37:42.038600Z

Yeah

Hukka 2021-06-07T09:38:31.038800Z

But on babashka even that works :thinking_face:

Hukka 2021-06-07T09:38:45.039Z

bb -e '(bigdec "1")' that is

borkdude 2021-06-07T09:38:53.039200Z

this is because babashka has this class in its reflection config

Hukka 2021-06-07T09:38:57.039400Z

Ah!

borkdude 2021-06-07T09:39:51.040300Z

@alexmiller It seems bigdec is using reflection to create a BigDecimal from a string. Perhaps the function could add a special case for a string?

Hukka 2021-06-07T09:46:02.040400Z

Had to do a bit of reading, but it works now! Thanks!

Hukka 2021-06-07T09:46:44.041300Z

Seems like babashka has some kind of way to automatically catch reflection problems to generate the config? Have to investigate that for robust builds

borkdude 2021-06-07T09:47:09.041400Z

:thumbsup:

borkdude 2021-06-07T09:47:41.042Z

@tomi.hukkalainen_slac It doesn't have that, but I do generate the reflection config programmatically using a script

borkdude 2021-06-07T09:47:54.042300Z

It has a curated list of classes it includes for interop

Hukka 2021-06-07T09:49:09.042800Z

Ok, so just test the binary carefully to catch those ๐Ÿ˜•

borkdude 2021-06-07T09:54:49.043700Z

Yes. There is an agent which can help you find reflective usages, but it emits a lot of false positives for Clojure programs

borkdude 2021-06-07T09:54:58.043900Z

However, there might be some useful clues in there

Hukka 2021-06-07T09:59:23.045600Z

Also the startup time improved from 1.5sโ†’7ms but actual processing went from 19sโ†’51s. Though I knew that the JVM is optimized for long running processes, I was surprised that it hits that hard even with less than a minute of runtime.

borkdude 2021-06-07T10:03:06.046300Z

well yes, the JVM has JIT which optimizes at runtime. a native-image is AOT-ed instead of JIT-ed.

borkdude 2021-06-07T10:03:30.046800Z

the enterprise version supports profile guided optimizations though, so you can optimize your native image for specific usages

borkdude 2021-06-07T10:05:41.047200Z

for anything longer running than 5 seconds the JVM probably makes more sense

borkdude 2021-06-07T10:06:30.048Z

GraalVM is also a JVM implementation so you could try to compare its performance with normal OpenJDK. It should be more performant there too, if not, they consider it a bug

Hukka 2021-06-07T10:06:57.048300Z

Good point, I'll do that too

Ben Sless 2021-06-07T10:20:05.048400Z

A lot of Clojure's reasonable performance is thanks to the JIT. Consider this edge case I came across https://github.com/bsless/clj-fast/issues/19

Hukka 2021-06-07T10:32:21.048900Z

Using graalvm as JVM gets the processing time down to 13s

Hukka 2021-06-07T10:59:17.049500Z

Seems like I don't actually need to set the -Dclojure.compiler.direct-linking=true though I had copied that from many examples at first. What does that really do, and when is it needed?

borkdude 2021-06-07T11:05:26.049900Z

it can make the binary size somewhat smaller in some cases. also it's a performance optimization

borkdude 2021-06-07T11:05:43.050300Z

in general what it does: it avoids var indirection when calling functions

borkdude 2021-06-07T11:06:11.050900Z

e.g. (defn foo []) (foo). The latter call goes via the var #'foo. When enabling direct linking, it will no longer go through the var, but will call the function directly

Hukka 2021-06-07T11:14:31.051500Z

I've seen it used with native-image, but that sounds useful even with just normal uberjars

borkdude 2021-06-07T11:24:08.051800Z

its useful in any final artifact

borkdude 2021-06-07T11:24:28.052Z

although it comes with trade-offs

borkdude 2021-06-07T11:24:32.052300Z

we used to have it on in production

borkdude 2021-06-07T11:25:12.053300Z

but this also takes away some power e.g. for patching some var real quickly without re-deploying, which is probably a very niche case, but can be useful for a quick debugging session

Hukka 2021-06-07T11:26:13.053800Z

Yeah, I've heard that some people leave nrepl or something open on every deployment for such

borkdude 2021-06-07T11:26:33.054200Z

yep, we do that

Hukka 2021-06-07T11:26:35.054300Z

But that idea gives me the heebie-jeebies

borkdude 2021-06-07T11:27:15.054800Z

I also use it for things you would normally expose to UI or endpoint, e.g. run special tasks

borkdude 2021-06-07T11:28:06.055100Z

greater power comes with greater risk, make your own trade-offs

Hukka 2021-06-07T11:28:31.055300Z

Sure

Hukka 2021-06-07T12:11:48.056400Z

Hm, the included musl libs in GraalVM don't have libz.a, but choosing that as libc will still use -lz. Have to figure out how to pass extra paths

borkdude 2021-06-07T12:14:01.057600Z

@thiagokokada and @rahul080327 can tell you more about it perhaps

kokada 2021-06-07T12:15:50.059800Z

Yeah, you need to build zlib using musl-gcc and install it at the correct location so the linker can find it

kokada 2021-06-07T12:16:03.060400Z

Remember to do a static build of zlib

Hukka 2021-06-07T12:16:03.060500Z

Ah, I passed over that too quickly. I somehow read it so that installing the musl-tools will be enough, and didn't read through the PR

Hukka 2021-06-07T12:16:32.060900Z

But of course the distro version of zlib is for glibc, not musl

Hukka 2021-06-07T12:17:24.061500Z

https://github.com/lread/clj-graal-docs seems like a critical resource to get anything working, thanks for that. Wish I had found that first, not the numerous old blog posts

Hukka 2021-06-07T12:18:51.062800Z

Musl installation is a bit too much of a hassle to do manually, so I'll defer that until I have a container set up for building the whole thing, so that everyone else can then just use that to get everything working across distros

Karol Wรณjcik 2021-06-07T12:29:29.063400Z

@tomi.hukkalainen_slac You can find it useful regarding zlib and musl: https://github.com/FieryCod/holy-lambda/blob/master/docker/ce/Dockerfile

Ben Sless 2021-06-07T12:31:54.063700Z

It gives at least 5% throughput improvement from my experience It also significantly reduces native-image build time

Hukka 2021-06-07T12:34:06.064100Z

Thanks, I'll get back to this soonish, I hope

borkdude 2021-06-07T14:47:57.065300Z

Just FYI, I think @raspasov or @smith.adriane was asking about this (can't remember) Someone on the GraalVM slack just mentioned they are going to production with a GraalVM native-image based iOS app: https://apps.apple.com/us/app/cospaces-edu/id1224622426

5๐Ÿคฏ
phronmophobic 2021-06-07T15:37:18.066200Z

I was definitely interested in investigating how to target mobile with native-image. I got some good answers in the graalvm slack. I was able to get the HelloGluon example running on my phone this weekend.

raspasov 2021-06-07T19:11:32.067100Z

Very neat.

raspasov 2021-06-07T19:36:21.067400Z

@smith.adriane were you using Clojure + GraalVM to target iOS/Android?

phronmophobic 2021-06-07T19:39:24.068Z

I would like to.

raspasov 2021-06-07T19:40:25.068200Z

@borkdude do you happen to know what language they were using to make the CoSpaces Edu app? Was it Java?

raspasov 2021-06-07T19:43:38.068400Z

@smith.adriane yes ๐Ÿ™‚ That would be neat; I have a hard time imagining how the interop would look like with something like SwiftUI, but it would be cool if itโ€™s possible.

borkdude 2021-06-07T19:44:12.068600Z

I don't know, ask them :)

1๐Ÿ‘
phronmophobic 2021-06-07T19:46:33.069700Z

objective c is pretty dynamic. I think interop could actually be pretty usable

raspasov 2021-06-07T19:46:52.069900Z

Objective-C, yes. Swift is what is more of a question mark.

raspasov 2021-06-07T19:47:19.070200Z

SwiftUI is only available from Swift. And itโ€™s kinda neat the way it works, itโ€™s more or less similar to React in terms of state management.

phronmophobic 2021-06-07T21:14:25.071400Z

Not a super impressive program, but I was able to get clojure to run on my iPhone using native-image!

(ns com.phronmophobic.mobiletest)

(defn clj-sub [a b]
  (- a b))

8๐ŸŽ‰