Campfire port in Java is faster then Rust. Some thoughts about JEP 544 (AOT Code Compilation)
DHH is porting campfire to Rust. I saw port in C# so I was wondering about Java performance. I asked Claude to do the migration, and even though the first version was faster, it was very far from idiomatic Java. I tried to close the gap, but some compromise had to be reached in order to make this work as a drop-in replacement.
Also, some typical Java approaches were just slower. Probably both the Rust and Java implementations could probably do better in terms of performance and being closer to how idiomatic code is written by everyday developers
Stack is Java + quarkus.
Beside just Java migration I tested some new feature like AOT with training introduced in Java 28 (EA build). My main conclusion is - its hard to beat a language that will adjust code to how your app is used. Its more about the JVM than Java.
I tried different combinations of AOT also with Graalvm CE. I didn't put much effort into making graal more optimal. Probably fair comparision would be to also do training run and add EE edition.
What I notice, and it's obvious in hindsight, AOT helps when end usage is same as training usage. When id differs code it optimized for different path and it hurts execution. It's not so visible in benchmark numbers but on smaller and shorter runs it was more visible. JIT needs time to adjust.
Edit: Repo: https://github.com/kgonia/once-campfire-java
8
u/BenchEmbarrassed7316 8d ago edited 8d ago
Correction: this is incredibly bad vibe code in Rust. Under normal usage, agents will generate much better code.
I just opened a random file:
https://github.com/basecamp/once-campfire-rust/blob/main/crates/cable/src/naming.rs
fn underscore(word: &str) -> String { let chars: Vec<char> = word.replace("::", "/").chars().collect(); let mut out = String::with_capacity(chars.len() + 4); for (i, &c) in chars.iter().enumerate() { /* ... */ } // ... }Let me explain: the function takes a reference to a buffer containing a string.
.replaceallocates memory for a new string and copies the buffer into it, replacing specific characters. Then,.chars()creates an iterator from this string (creating iterators in Rust is inexpensive, and the compiler can sometimes optimize them even better than standard loops)..collect()creates yet another buffer and copies the iterator's output into it - which is completely pointless. However, this is followed by an "incredible" optimization:String::with_capacity(chars.len() + 4). Instead of allocating a buffer of an unknown size, the code explicitly specifies that the buffer should have a size equal to "the length of the string inchars+ 4." In other words, to allocate the required amount of memory, it performs an allocation, writes data into it, and then checks the size of the allocated memory. This is absolute nonsense.I don’t know how he got the AI to generate such bad code.
added: Furthermore, this optimization is incorrect because the input data is in UTF-8, yet the buffer allocation is based on the number of characters rather than the number of bytes. Once again, I have no idea what kind of agent could make such elementary mistakes.