Skip to content

CrucibleVM 0.1.0

Pre-release
Pre-release

Choose a tag to compare

@jnbdz jnbdz released this 13 Oct 22:06
· 38 commits to crucible/main since this release

CrucibleVM 0.1.0 is the first downloadable build: open-source profile-guided optimization for GraalVM Community Edition native-image, on GraalVM CE 25.4.4.1.1 (JDK 25), Linux x86-64.

Alpha. Measured, and checked for identical output, on a handful of benchmarks; not yet on 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.1.0-linux-x86_64.tar.gz
cruciblevm/crucible/pgo.sh --help

The 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; checked on Ubuntu 24.04 in a clean container.

Build an optimized executable in one command

cruciblevm/crucible/pgo.sh --name app --run '$APP <your workload arguments>' -- -cp app.jar com.example.Main

It 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.

What it does

With a profile, the image is built at -O3, as Oracle's is, and the profile gives the compiler branch probabilities and call counts (counted by probes placed before inlining, so a context's profile is not one-sided), receiver types with eight frames of calling context, sampled call stacks for hotness and self time, a copy of a hot method for each hot calling context, and the list of the types the run locked, so the types it used and never locked are built without a monitor slot. The serial collector copies the old generation instead of compacting it, prefetches what the objects it copies point to, promotes straight from eden while most of what survives one collection survives the next, and System.arraycopy goes to memmove with one card mark per copy. Method handles that Jackson makes at run time are compiled instead of interpreted.

Without a profile, the default level keeps the -O3 inliner's typical graph size, and the branches of the class library get the probabilities recorded for them in other programs, from a profile of the library that comes with the builder (-H:-CrucibleLibraryProfile turns it off).

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
Renaissance, fourteen benchmarks, geometric mean of the time 1 0.87 to 0.89
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,060 105,200
Spring PetClinic, requests a second on /vets (JSON) 36,800 46,500

Ahead on twelve or thirteen of the fourteen Renaissance benchmarks depending on the session, par-mnemonics by 29% and scrabble and scala-doku by 23%; behind on rx-scrabble by 2 to 4%, and level to 2% behind on scala-stm-bench7. Without a profile, at the default level, the fourteen run at 0.917 of Oracle's default binary, ahead on thirteen and level on rx-scrabble, for images 17 to 27% larger than Oracle's. 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 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 2.5 GB where Oracle's binary peaks in 0.9; give such a program an -Xmx of about three times what it keeps alive.

Checksum

9efb9699b93bf28ae8fbca11c2b1c24390b082340facda0e0ec96d87ad27801e  cruciblevm-0.1.0-linux-x86_64.tar.gz