
Native Clojure with GraalVM - tosh
https://janstepien.com/native-clojure-with-graalvm/
======
wink
After hearing about GraalVM first time at a PolyConf talk last year I sat down
with one of the GraalVM folks and we (well, I was just providing info) did a
few performance measurements if this could speed up the startup time
(especially for leiningen tasks) - the preliminary results were pretty cool.

So if you're using Clojure and Leiningen and are annoyed by the long startup
times... there's potential :)

~~~
zcam
It's not all unicorns though.

Performance can vary quite a lot from task to task, also compile times suffer
a lot and you lose some dynamic features.

So from what I understand it _might_ be useful for clojure cli apps, but at
this point I am not sure it brings a lot, but this is early days.

The cool part is the polyglot capabilities imho.

Then there's the elephant in the room: the licensing and potential changes in
the long run.

For clojure if you want decent cli/startup experience there's always cljs,
lumo or you can go with something like fennel-lang, a clojure looking lisp
that "transpiles" to lua(jit), so 0 overhead compared to lua and super fast
startup.

[https://fennel-lang.org/](https://fennel-lang.org/)

~~~
wink
Yeah, but I don't see anyone rewriting leiningen in cljs anytime soon.

For general CLI tools, yes, I'd also look into cljs at this time.

~~~
zcam
You'd be surprised, there are people in the community with some clever ideas
to do that with a (fast)client/server model and change diffing (incremental
builds), but it's super early days.

With tools.deps and the need of some companies for more advanced build setup a
lot of people are actually thinking about these issues.

~~~
wink
I'm not denying that someone, somewhere is working on something.

But I'd be surprised indeed, I'm not watching the whole clojure space closely,
I can only say that no one hit the #leiningen irc channel or posted something
on the bugtracker/proposed a PR yet.

And the client/server model.. sure, nailgun and grenchman were a bit clunky,
but none of these efforts have gained widespread adoption, afaik. Most people
don't just go out of their way to tweak their build tool.

~~~
zcam
> And the client/server model.. sure, nailgun and grenchman were a bit clunky,
> but none of these efforts have gained widespread adoption, afaik. Most
> people don't just go out of their way to tweak their build tool.

I was not thinking about these two, nor was I thinking about a patch for
boot/lein.

That said the slow startup issue is only really valid for cli apps (and maybe
CI), if your dev workflow relies on re-starting your repl all the time, it's a
broken workflow.

~~~
jjwiseman
There is a group of people that love to say this ("If you're not using a repl
or restarting your repl all the time, you're doing it wrong"), but I've been
using Lisp for more than 20 years and I don't agree. A repl is nice, and I use
one often, but it does have tradeoffs. One is persistent state--It can be
quite easy to get your repl into a state where your code works, but if you run
the app from scratch it doesn't because definitions are out of sync (e.g. you
renamed a function, but forgot to change the name at a call site, so you end
up calling an old version of the function.)

Developing in a repl is nice, but it's not the only allowed or legitimate or
even good way to do development in Lisp or other dynamic, interactive
languages. Clojure has an issue with slow startup (another tradeoff made for
good reasons that don't fit everyone's situation)--denigrating other people's
development approaches doesn't solve that, it comes off as defensive.

~~~
kazinator
> _I 've been using Lisp for more than 20 years and I don't agree. A repl is
> nice, and I use one often, but it does have tradeoffs._

Me neither, and I also carry decent Lisp cred.

I know what I'm doing to the extent that I can write a bunch of code in a
file, and have it work, more or less.

If I experiment with changes to code, I want to see the diff (as in git diff).

Actually _developing_ in a REPL seems very scatter-brained; you don't know
what you're running since you've been mutating things left and right.

Those that develop in a REPL actually develop in some kind of text editor
which sends expressions to the REPL; that's what allows them to save the code
properly to a file. Problem is, you now have three versions of the code to
keep straight: editor, file and image.

Okay, so you have massaged things into working. Well, which is the code that
is working? Obviously, the code that is in the image. But of the textual codes
does it correspond to? If you just save the content of every edit buffer to
disk, is the code on disk exactly that code that is working in the image?

Even if I have to work with a running image (say there is a lot of state that
is difficult to reproduce from a clean start), I'm still going to work with
some files, which I will edit and completely save to disk, and load all
together as a unit into the running image, instead of evaluating individual
expressions out of an edit buffer.

------
pankajdoharey
GraalVM is very impressive. But clojure is very much associated with Java/JVM
Ecosystem which makes it very hard to convince people to use it. My ruby
friends would say oh i cant install clojure because i have to install JVM.
Clojurescript like lumo or planck is still better, but it isnt a real backend
solution. Clojurescript is great for the frontend though. I think clojure
needs a native solution written in Clojure + LLVM that leverages existing
C-libs. A seperate JIT/compiler that is not JVM, that is not a language with
Borrowed GC + VM.

~~~
unmole
I don't understand this sentiment. How is installing JRE any diffierent than
installing Ruby?

~~~
pankajdoharey
I am not sure how do i explain this, but a lot of people think about JVM with
verbose slow VM code that works in the enterprise. java binary doesnt even
follow unix styled cli options. the worded cli options are single hyphenated.
There are many oddities associated with JVM/Java which are purely superficial.
But yet a lot of people think it is irritation to use JVM, plus the perceived
notion of Enterprise bloatware.

~~~
klibertp
> a lot of people think about JVM with verbose slow VM code that works in the
> enterprise

They are severely misinformed, then, and it would be a good deed to give them
an opportunity to learn more about this.

Which is: Java is fast. Its startup time is impressive, and the runtime
performance is generally also good. RAM usage could be less impressive, but I
read that it can be optimized for memory consumption, achieving pretty good
results - I don't have any experience here, so I can't really say.

Anyway, my problem with Java, is that it is not designed to be used on its
own. To use it, you need a build system, and the language doesn't give you
one; which is why there are many different systems, which you need to navigate
somehow. Having to learn these build system (XML? custom Groovy
implementation?) is what made me dislike Java. It's not that different in C++
or C#, but it adds to the feeling that you work with an ancient technology,
not a modern one.

That's however not the problem with Clojure - it at least has a single build
system + package manager - Leiningen. To me, what disqualifies it, is the
startup time of its REPL. Well, that's only because I have a luxury of not
needing to do anything on the JVM - so I'm speaking from a position of a
hobbyist polyglot programmer considering new languages to learn. I would
probably reconsider if I had a job where JVM would be a must, but the specific
language was not yet chosen (how probable that would be is another matter).
Anyway, back on topic: I don't have Clojure on this machine, but last time I
tried it, it took more than a second before the prompt appeared. That is slow
even when considered on its own, but it borders ludicrous (speed) when
compared with other Lisps, which seems to be the right frame of reference
here. In that comparison, it's... Well, just to let you know how bad it is,
Common Lisp and Chicken Scheme, two Lisps I do have on this machine, have
startup (+ shutdown!) times like this:

    
    
        -▶ time sbcl --eval "(exit)"
        This is SBCL 1.4.6-1.fc28, an implementation of ANSI Common Lisp.
        More information about SBCL is available at <http://www.sbcl.org/>.
    
        SBCL is free software, provided as is, with absolutely no warranty.
        It is mostly in the public domain; some portions are provided under
        BSD-style licenses.  See the CREDITS and COPYING files in the
        distribution for more information.
        sbcl --eval "(exit)"  0,00s user 0,00s system 94% cpu 0,006 total
    
    
        -▶ time csi -e "(exit)"
        csi -e "(exit)"  0,00s user 0,00s system 93% cpu 0,005 total
    

Now, two things:

1) I get it that "you're supposed to have a deamon process in the background,
so you don't need to start that often", but this, in itself, is basically
announcing surrender. In Common Lisp you're supposed to do the same, but not
by necessity!

2) Yes, I tried alternative runtimes/implementations. Indeed, they are better.
However, until they get, provably, 99% the same semantics as the reference
implementation, they won't be adopted by any significant amount of people,
which has enough of the drawbacks to make me not seriously consider using
them. Anecdotally, last time I looked, I've seen lots of lein plugins for
setting up projects with ClojureScript on the frontend and some (pure) Clojure
Web framework on the backend. There has to be a reason behind that.

TLDR: Java is very fast, Clojure uses JVM in ways which are inherently slow on
that VM, but refuses to honestly migrate to a more suitable platform, because
of Java ecosystem, which is also a major selling point of the language.

At least, that's my understanding of the situation - if I'm wrong, I'd be
happy to get corrected.

~~~
pankajdoharey
I understand, i too had the same apprehension about clojure/JVM before i used
it. Though the startup time of clojure was slow, Java is anything but slow.
The startup tie has improved quite a low in clojure the past few months, but
leiningen is still a pain to use.

> TLDR: Java is very fast, Clojure uses JVM in ways which are inherently slow
> on that VM.

JVM is really not the best platform for hosting either dynamic languages or
functional ones.

Heres the time taken by csi, clojure , racket , sbcl. >> time csi -e "(exit)"

real 0m0.088s user 0m0.020s sys 0m0.027s

>> time clj -e "(System/exit 0)"

real 0m1.944s user 0m3.272s sys 0m0.253s

>> time racket -e "(exit)"

real 0m1.100s user 0m0.503s sys 0m0.296s

>> time sbcl --eval "(exit)" real 0m0.060s user 0m0.023s sys 0m0.025s

>> time newlisp -e "(exit)"

real 0m0.089s user 0m0.011s sys 0m0.033s

Clojure is the slowest in this.

------
chii
For many real world applications, you'd need to load classes that isn't known
at compile time (e.g., a user supplied database jdbc connector).

I wonder how AOT can deal with this scenario. Is it possible to AOT but still
have the fallback of classloading at runtime?

~~~
Twirrim
> For many real world applications, you'd need to load classes that isn't
> known at compile time (e.g., a user supplied database jdbc connector).

I'm not familiar with some of the dev patterns here, but why not just load all
the classes for the databases you want to support, from the top, and compile
them all in? Is that not practical?

~~~
chii
> just load all the classes for the databases you want to support

you cannot distribute some connectors (e.g., the oracle connector or the mysql
connector), in the case of jdbc anyway.

And it's not always possible to know ahead of time the classes you want to
pre-load, if your application is like eclipse with a plugin architecture which
can allow users to install custom plugins.

------
mping
There's some libs that don't quite work with GraalVM yet, due to some
unsupported features. I'm guessing these issues will be solved quickly enough,
and then we will be able to deploy small binaries :)

------
dancek
I saw the talk live last Friday in ClojuTRE. Someone asked about performance,
and IIRC the speaker said that a GraalVM-built native binary tends to run
about 50% slower than JVM for long-running tasks.

For some purposes this matters and for some others it doesn't. I can certainly
see the benefit of quick startup for e.g. CLI tools. And for servers, the
security benefit of running a single standalone binary blob inside a
multistage Docker container might be easily worth it.

------
tosh
talk:
[https://www.youtube.com/watch?v=kjZP_wBQJ2U](https://www.youtube.com/watch?v=kjZP_wBQJ2U)

tl;dr: surprisingly Clojure code benefits a lot from aot compilation

------
mark_l_watson
At a minimum this will be very useful for command line utilities: small memory
footprint and good runtime performance. I used to use Gambit Scheme for
command line utilities for the same reasons.

------
bad_user
GraalVM is very cool, but AOT compilation has caveats too. No more profiling
and optimizing the code at runtime for example.

I wonder what the performance impact will be for Clojure code.

~~~
pjmlp
That is why AOT compilers also support PGO, including Graal.

~~~
divs1210
Graal PGO seems to be enterprise only.

[https://github.com/oracle/graal/issues/447#issuecomment-3995...](https://github.com/oracle/graal/issues/447#issuecomment-399524000)

~~~
pjmlp
So? It is still a feature.

Not everything needs to be free beer.

------
truth_seeker
Support for Reflection and Dynamic class loading isn't possible yet, which
could be a big downside. I hope 1.0 GA release lands with it.

~~~
zebraman
Reflection is partially emulated, IIRC.

------
divs1210
The native binary produced by Graal CE does not support runtime optimizations
- that is EE only.

As such, native binaries will start faster, but run slower.

[https://github.com/oracle/graal/issues/447#issuecomment-3995...](https://github.com/oracle/graal/issues/447#issuecomment-399524000)

------
crudbug
I think .Net folks are also doing similar things on CoreRT side ?

~~~
pjmlp
Yes, however.NET has standard AOT support as part of its toolchain.

NGEN, MDIL for WP 8.x, .NET Native for UWP, Xamarin AOT, Unity IL2CPP, and the
internal compilers used at Singularity and Midori.

------
rapsey
> there's potential :)

To be owned by Larry Ellison.

~~~
wink
Valid point, but to a degree I still see this for the whole Java ecosystem.
Many people over the last 20 years were dissuaded from using Java just because
of the looming Oracle "threat".

I'm not advocating to make GraalVM a mandatory dependency, just saying this
could be an interesting project for people who are annoyed at the status quo,
and there are a lot of startup speed complaints.

Unfortunately my day job has nothing to do with any JVM or Clojure anymore, so
I can't divert any time there.

~~~
zcam
Oracle didn't own java 20 years ago :)

That said there's a lot of FUD around Oracle ownership nowadays but most of it
is moot since OpenJDK is the fundation of Oracle's Java.

~~~
dnomad
GraalVM is itself an open-source project that is licensed under the exact same
license as OpenJDK [1]. Feel free to download it and build it yourself to be
extra sure [2]. All the FUD around this stuff and Oracle is a bit comical.
It's all... right there.

[1] [https://www.graalvm.org/docs/faq/](https://www.graalvm.org/docs/faq/)

[2] [https://github.com/oracle/graal](https://github.com/oracle/graal)

~~~
ptx
But it's not "all" there - some things are reserved for the enterprise
edition. So if people start adopting it now in the hope that its remaining
technical limitations will eventually be adressed, we might discover that the
improvements only apply to the EE version, while the open source version is
kept more or less where it is to encourage people to pay up.

~~~
dnomad
This is true of every freemium project, from Gitlab to all the commercial
Linux distros that sell support+prop extensions on the side. Given that
GraalVM is a virtual machine there's strong incentive to push it far and wide.
History suggests that VMs that are free and widely available survive and
thrive (eg Java and .NET) while VMs that are expensive and/or difficult to
redistribute die (eg Smalltalk). Oracle's incentives here, seeing as they want
languages and library developers and ultimately application developers to
actually deploy on GraalVM, is to go "far and wide" and not deep and narrow
when it comes to their customers.

> its remaining technical limitations will eventually be adressed

It's not clear there are any technical limitations that separate CE and EE.
Supposedly EE is faster but I don't think anybody has proven this with real
benchmarks. All of this is clearly documented in
[https://www.graalvm.org/docs/faq/](https://www.graalvm.org/docs/faq/).

~~~
divs1210
The native binary produced by Graal CE does not support runtime optimizations
- that is EE only. As such, native binaries will start faster, but run slower.

[https://github.com/oracle/graal/issues/447#issuecomment-3995...](https://github.com/oracle/graal/issues/447#issuecomment-399524000)

