Benchmarks

Measured performance comparing hx to cabal, with honest results — where hx wins and where it doesn't

Performance Benchmarks

Numbers, not adjectives. Here’s the methodology and the measurements behind hx’s speed claims — and an honest account of where hx is not faster.

Measured with hx 0.8.0. hx is faster than cabal on all four operations measured here — cold builds, CLI startup, no-op incremental rebuilds, and clean. stack was not re-measured for this release, so it is omitted rather than estimated.

Test Environment

PropertyValue
hx version0.8.0
GHC version9.8.2
Cabal version3.12.1.0
stacknot measured
PlatformmacOS, Apple M4 (10-core)
Toolinghyperfine 1.20.0 (6–30 runs, 1–5 warmup)
Date2026-06-21

Results

Test project: a simple 3-module executable (Main.hs, Lib.hs, Utils.hs) depending only on base — the case hx’s native build mode targets.

Operationhx (--native)cabalResult
CLI startup (--help)3.2 ms18.6 mshx 5.8× faster
Cold build (clean state)0.39 s2.04 shx 5.2× faster
Incremental (no changes)3.2 ms18.2 mshx 5.7× faster
Clean4.7 ms18.9 mshx 4.1× faster

Where hx wins

hx is faster than cabal on all four operations measured here — cold builds (≈5.2×), CLI startup (≈5.8×), no-op incremental rebuilds (≈5.7×), and clean (≈4.1×). The native build path invokes GHC directly, skipping cabal’s package-database queries and build-plan calculation, and hx is a native Rust binary with no GHC-runtime startup cost.

Two of those weren’t always wins, and we’d rather say so than airbrush it: the no-op rebuild used to spend ~74 ms spawning ghc/ghc-pkg before realizing nothing had changed, and clean used to spin up the plugin runtime needlessly. Both paths were short-circuited. Earlier published cabal figures for incremental and clean were also overstated; these numbers are measured fresh.

Dependency resolution

hx lock reads a cached, pre-parsed Hackage index, so a warm lock of a project with real dependencies is ~37 ms. The first lock after the index changes parses the full ~90 MB index — evaluating each package’s .cabal conditionals — in ~4.9 s, a one-time cost that is then cached for 24 hours. The warm path is unaffected.

Native Build Mode

hx’s native mode bypasses cabal entirely for simple projects: direct GHC invocation, no package-database queries or build-plan calculation, content-hash fingerprint caching, and native parallel compilation.

When native builds apply

ScenarioNative build?
Single-package projectYes
Only base dependenciesYes
Multiple external dependenciesNo (falls back to cabal)
Custom Setup.hsNo
C FFI / foreign librariesNo

Reproduce These Numbers

cargo install hyperfine

mkdir /tmp/hx-bench && cd /tmp/hx-bench
hx init bench --name bench
# (3-module base-only project — see docs/BENCHMARKS.md for the exact files)

# cold build (clean before each run)
hyperfine --warmup 1 --prepare 'rm -rf .hx dist-newstyle' 'hx build --native' 'cabal build'

# incremental, no changes (warm up first)
hx build --native && cabal build
hyperfine --warmup 3 'hx build --native' 'cabal build'

Full methodology and the exact test files are in docs/BENCHMARKS.md.

Not re-measured for 0.8.0

Project init, single-file-change incremental builds, preprocessor overhead, and memory usage were measured on older releases but have not been re-run for 0.8.0. Rather than present stale figures as current, they’re omitted here. Contributions welcome — open an issue.