Repository navigation
CrucibleVM 0.3.0
Pre-releaseCrucibleVM 0.3.0: open-source profile-guided optimization for GraalVM Community Edition native-image, on GraalVM CE 25.4.4.1.1 (JDK 25), Linux x86-64. The third downloadable build; 0.2.0 was on 2026-10-24.
Alpha. Measured, and checked for identical output, on a handful of benchmarks and two services; the builder's own unit tests now pass with a profile and in a recording image, but this is not yet the breadth of code a production compiler has to get right. Options and the profile format may change. Keep a build without a profile to compare against, and report anything that miscompiles.
Install
tar xzf cruciblevm-0.3.0-linux-x86_64.tar.gz
cruciblevm/crucible/pgo.sh --helpThe archive is a GraalVM JDK with native-image and the CrucibleVM scripts; it needs neither mx nor the source tree. native-image needs a C toolchain: gcc, glibc-devel and zlib-devel on Fedora or Rocky, gcc libc6-dev zlib1g-dev on Debian or Ubuntu; the profile scripts need python3. Built on Rocky Linux 10.
Build an optimized executable in one command
cruciblevm/crucible/pgo.sh --name app --run '$APP <your workload arguments>' -- -cp app.jar com.example.MainIt builds an image that records, runs your workload on it, builds an image that samples call stacks, runs the workload again, and builds ./app, the image to ship. The arguments after -- go to native-image as they are. For a service, --run is a script that starts the image, puts load on it and stops it with SIGTERM (build with --install-exit-handlers). --no-samples stops after the counted profile. The steps one at a time, merging the profiles of several runs, and converting an Oracle GraalVM .iprof are in the user guide.
New in 0.3.0
- The serial collector collects the old generation by default once it holds twice what the last complete collection kept (16 MB at the least). 0.2.0 shipped this as the opt-in
-XX:SerialGCOldGrowthFactor=2; it is the default now, and-XX:SerialGCOldGrowthFactor=0waits for the heap to fill as before. Spring PetClinic under load holds 240 to 280 MB and not 311 (Oracle's binary 200) and answers 1% more requests; Quarkus is unchanged. On the fourteen Renaissance benchmarks it costs about 1% over all: par-mnemonics 3 to 6%, mnemonics 1 to 3%, and the two peak at about 165 and 203 MB and not 195 and 253. -XX:SerialGCMinDistancemeasured as a curve, still off by default. Against the 20 ms default on the fourteen Renaissance benchmarks: 30 ms runs 1% faster for 11% more memory in all, 50 ms 3.3% for a third more, 75 ms 3.9% for two thirds more, 100 ms 4.3% for 86% more; par-mnemonics gains 11% at 50 ms, reactors 6%, mnemonics and fj-kmeans 4%, while philosophers, future-genetic and scrabble peak at two to three times their memory. The Quarkus service answers 3% more requests a second at 30 ms (90 MB and not 86), 8% at 50 (106 MB), 11% at 100 (145 MB, Oracle's binary 140 to 170); Spring PetClinic is unchanged. Pick a value by what memory the program may take; the numbers are incrucible/README.md.
How it compares
Same machine, each binary checked for identical output, time of the profile-guided binary from each compiler; lower is better. Oracle GraalVM 25.0.4, which has Oracle's PGO.
| workload | Oracle GraalVM | CrucibleVM | CrucibleVM, parallel young collection |
|---|---|---|---|
| Renaissance, fourteen benchmarks, geometric mean of the time | 1 | 0.86 | 0.83 |
| GameOfLife (Oracle's own PGO example) | 3.39 s | 2.91 s | |
| ArrayBench | 1.19 s | 0.31 s | |
| BranchBench | 0.55 s | 0.54 s | |
| JsonBench | 1.56 s | 1.39 s | |
| BenchPGO | 0.37 s | 0.32 s | |
Quarkus REST/JSON quickstart, requests a second on /fruits |
100,600 | 107,400 | |
Spring PetClinic, requests a second on /vets (JSON) |
36,800 | 47,200 |
Ahead on thirteen of the fourteen Renaissance benchmarks; behind on rx-scrabble by 2 to 3%, which is the collector's sizing under that harness rather than the compiler. Spring PetClinic, built by Spring's own native build and measured over 90 s of load, answers 31% more requests a second than Oracle's profile-guided binary, Quarkus 3% fewer; Spring's memory under load is above Oracle's (see the old-growth option above). Without a profile, at the default level, the fourteen run at 0.917 of Oracle's default binary. The measurements, the method and everything tried that did not work are in crucible/README.md, crucible/ROADMAP.md and docs/issues/.
Known limits
- Linux x86-64 only; nothing else has been built or tested.
- The parallel young collector is new, see above.
- The recording image is larger and slower than Oracle's: 100 to 140 MB where Oracle's is 50 to 65, and scrabble records at 3.2 s an iteration where Oracle's recording image takes 2.4.
- A type the workload used without locking and production locks heavily is locked slowly; record a workload that locks what production locks.
- akka-uct peaks in about 2 GB where Oracle's binary peaks in 1; the policy grows the young generation to 1 GB because the program spends half its time in young collections at any size. Capped at
-Xmx1git runs 5% slower in 1.05 GB and at-Xmx700m10% slower in 0.72 GB, still 20% and 15% ahead of Oracle's binary; cap such a program by what it may take.
Checksum
dd5f447ff56e61c727fb54abebbe2a7306031557fde65c00d1801005ffe316d6 cruciblevm-0.3.0-linux-x86_64.tar.gz