Skip to content

Releases: SiteNetSoft/crucible

CrucibleVM 0.4.0

CrucibleVM 0.4.0 Pre-release
Pre-release

Choose a tag to compare

@jnbdz jnbdz released this 30 Oct 16:40
97ff2e1

CrucibleVM 0.4.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 fourth downloadable build; 0.3.0 was on 2026-10-26.

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

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.

New in 0.4.0

  • The young generation is collected on several threads by default. 0.2.0 and 0.3.0 shipped this as the opt-in -H:+ParallelYoungCollection; build with -H:-ParallelYoungCollection to keep collections on one thread. -XX:ParallelYoungCollectionThreads=<n> sets the thread count at run time; the default is the processors the program may use, which in a container is its CPU limit (up to 9), and with one processor collections stay serial. On eight processors the fourteen Renaissance benchmarks run 3.6% faster than serial (geometric mean; scala-kmeans loses most, 1.6%), and level on two and on one; akka-uct runs 15% faster and peaks at 1.2 GB and not 1.9, scala-stm-bench7 and reactors 12% faster, scrabble 7%. The Quarkus and Spring services answer as many requests in the same memory.
  • -XX:+SerialGCShrinkWhilePromoting, off by default, lets the young generation shrink for memory while objects are promoted, so it ends at the same size with or without -Xms. For a service with a capped heap, use it with -XX:SerialGCTimeRatio=19: under -Xmx512m Spring PetClinic answers 5,490 to 5,520 requests a second in 142 to 149 MB (Oracle's binary 4,460 in 198 to 204 MB, the default 5,690 to 5,800 in 237 to 242), and the Quarkus quickstart 15,760 to 15,940 in 113 to 118 MB (Oracle's 15,000 in 150 to 167, the default 14,450 to 14,640 in 86 to 88). Without a cap, ratio 19 makes programs peak at about twice their memory, so both stay off by default.

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

The fourteen Renaissance benchmarks: on eight processors with the parallel default, 0.832 of Oracle's time (0.864 serial). With the serial collector, ahead on thirteen of the fourteen and behind on rx-scrabble by 2 to 3%, which is the collector's sizing under that harness rather than the compiler; parallel collection costs rx-scrabble about 1% more. 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 shrink option above for a capped heap). 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.
  • akka-uct can hang, about once in two thousand runs: that is akka's own fork-join bug (akka/akka-core#33019), and it hangs the same way on HotSpot.
  • 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 -Xmx1g it runs 5% slower in 1.05 GB and at -Xmx700m 10% slower in 0.72 GB, still 20% and 15% ahead of Oracle's binary; cap such a program by what it may take.

Checksum

4ce58635378b2cf25d09cf1c1eeb845ffe06b23c39da0493b01cc815b8aaf773  cruciblevm-0.4.0-linux-x86_64.tar.gz

CrucibleVM 0.3.0

CrucibleVM 0.3.0 Pre-release
Pre-release

Choose a tag to compare

@jnbdz jnbdz released this 27 Oct 02:22
b9172ae

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

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.

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=0 waits 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:SerialGCMinDistance measured 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 in crucible/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 -Xmx1g it runs 5% slower in 1.05 GB and at -Xmx700m 10% 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

CrucibleVM 0.2.0

CrucibleVM 0.2.0 Pre-release
Pre-release

Choose a tag to compare

@jnbdz jnbdz released this 24 Oct 20:09

CrucibleVM 0.2.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 second downloadable build; the first was 0.1.0 on 2026-10-13.

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

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.

New in 0.2.0

  • Young collections on several threads, opt-in: build with -H:+ParallelYoungCollection; -XX:ParallelYoungCollectionThreads=<n> sets the number at run time (default: the processors, up to 9). With a profile, akka-uct runs 18% faster, scala-stm-bench7 20%, reactors 12% and scrabble 8 to 11%, and the fourteen Renaissance benchmarks go from 0.86 to 0.83 of Oracle's profile-guided binary. It stays off by default: one akka-uct run hung on 2026-10-17 and the cause has not been found, though more than 2,000 runs since, on every benchmark and under a copy check, have not hung. Use it where a hang would be noticed and tell us if you see one.
  • Two run-time knobs for the memory the serial collector takes, both off by default. -XX:SerialGCOldGrowthFactor=2 collects completely once the old generation holds twice what the last complete collection kept; with -Xms at the maximum heap the policy otherwise collects completely only when the heap is full, and Spring PetClinic under load then holds 311 MB; with the factor it holds 240 to 280 MB and answers 1% more requests (Oracle's binary holds 200). -XX:SerialGCMinDistance=50 lets the young generation grow while young collections are up to 50 ms apart instead of 20, which suits a program that drops everything at intervals: rx-scrabble comes level with Oracle's binary, for more memory on some programs. Both are measured in crucible/README.md.
  • Correctness. GraalVM's own native-image unit-test gate passes with a profile and in a recording image, under the builder's assertions. That found five bugs in the instrumentation, all fixed: a probe placed where objects were being moved, counters in methods with their own calling convention clobbering argument registers, and three in how the recording nodes declare the memory they touch. The build is clean with javac warnings as errors, and a GitHub workflow builds, runs checkstyle and the sample's record-and-rebuild check on every push.
  • -XX:+VerboseGC prints which rule sized eden at each young collection.

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 0.9; give such a program an -Xmx of about three times what it keeps alive.

Checksum

cf6821998e5f53e2dd85594a2875728ed2bc32873090a62553f38e011cea4351  cruciblevm-0.2.0-linux-x86_64.tar.gz

CrucibleVM 0.1.0

CrucibleVM 0.1.0 Pre-release
Pre-release

Choose a tag to compare

@jnbdz jnbdz released this 13 Oct 22:06

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