Clojure Web Server Performance

Round18 of TechEmpower Web Framework Benchmarks are out. reitit entry did well, being fastest of all Clojure, Scala and Kotlin entries (my favourite JVM languages) on physical hardware and second or first on cloud setup in all tests it was submitted. The JSON entry was only 3% slower than the fastest c++ entry. Not bad for a dynamically typed language using immutable data structures.

Clojure libraries used:

The async sql test was badly configured and it was much slower than the JDBC version, will try to make it good too, should be much faster (and non-blocking). Ideas and help on tuning the tests most welcome. Also, will start using pohjavirta as a simple and fast web server. Have toyed also with vert.x clojure bindings as the async SQL library used already uses it under the hoods.

Benchmarks shouldn’t be taken too seriously, but I think we can say Clojure can be fast too :wink:


Top Clojure, Scala & Kotlin entries on physical hardware:

16 Likes

Nice to see that you can “supercharge” next.jdbc by providing a custom builder!

… thanks to the awesome extension api in next.jdbc!

1 Like

Wow, @ikitommi ! This is awesome work. Very important for the community.

1 Like

what about widely using datafy on Java Object -> Clojure Data, will it faster? can be a wrapper for undertow run as fast as undertow?

Good question. datafy is an extra abstraction and doesn’t make things any faster, most likely, slower. Pohjavirta has currently basically the same perf as Undertow in the tests (±2%). In database test, the reitit entry is actually faster than the Undertow entry, with 360k TPS.

The tested apps are kinda bare bones, real life application perf is heavily effected by the middleware/interceptor libraries used.

Undertow3 will swap XNIO to netty, looking forward to seeing how it effects things.

So that means converting from java object to clojure data structure isn’t a problem? I was always thinking there’s a cost when parsing request and building response.

Copying from Java to Clojure is an issue performance wise. Both Aleph and Pohjavirta use a zero-copy request from Potemkin. With that, you pay an increased price on reading from request, but creating one is basically free. Ideal would be to get a ~free creation and pay a constant protocol method overhead to java classes on read, e.g. no map lookups involved, at the library level.

Related discussion in the ring repo.

1 Like

Any reason the most important benchmark (more “realistic”), fortunes is not there?

Fortunes involves server side templating, would need to find a fast clojure (java wrapper) for that first. My best guess would be to wrap rocker. Maybe later.

Small update, tests are now ported to pohjavirta & async postgresql client pooling works now correctly. The porsas async client seems bit faster than the synchronous one with much smaller connection pool (16 vs 256).

2 Likes

Hello @ikitommi!

It’s been interesting to follow your work in this thread. Thanks for posting!

Do you like the experience of working with performance sensitive code in Clojure compared to other languages? Does the REPL play a central role?

I’m kind of curious. I would think that ease of experimentation and ease of testing were advantages, but other ecosystems like Rust do have speed ingrained in their DNA, to a sense.

Teodor

Writing performant code with Clojure requires some extra knowledge on how things work under the hood (e.g. read the source, both clojure & java), some extra discipline, jvm interop and performance measurement tools. It’s much easier to be fast with statically typed languages, where things are usually fast by default. With Clojure, it’s not usually the default/idiomatic way to do thigs. Our open source library code looks very much different than the business code in the projects, performance versus clarity.

If you want to go fast with Clojure, good place to start would be http://clojure-goes-fast.com/ :wink:

For workflow, you don’t normally have to leave the REPL, profilers can hook into your running REPL and tools like criterium and clj-async-profiler are just libs you invoke when you need them.

2 Likes

Thanks for the explanation. I wasn’t aware that you could use the profiling tools from within the REPL – that’s encouraging!

1 Like

All tests are now ported to use pohjavirta, and after changing the strategy of Ring request mapping from buggy ZeroCopyRequest to PartialCopyRequest, the stack is starting to pretty fast :slight_smile:

(we are still 40% behind in the db-round-trip, have some ideas for that too)

3 Likes

@ikitommi, it has been fun watching you optimize this over the last several months. F*ck yeah! :grinning: