Benchmarks

Performance Benchmarks

hx is designed for speed. This page documents our benchmark methodology, test results, and how to reproduce them.

Test Environment

PropertyValue
hx version0.4.0
GHC version9.8.2
Cabal version3.12.1.0
Stack version2.15.1
PlatformmacOS (Apple Silicon M4)
DateJanuary 18, 2026

All benchmarks run on a quiet system with minimal background processes. Times are the median of 5 runs after 2 warmup iterations.


Executive Summary

Operationhxcabalstackhx Speedup
CLI startup12ms45ms89ms3.8x / 7.4x
Project init68ms320ms2.1s4.7x / 31x
Cold build (simple)0.48s2.68s3.2s5.6x / 6.7x
Incremental build0.05s0.39s0.52s7.8x / 10.4x
Clean8ms180ms95ms22x / 12x
Doctor/diagnostics45msN/AN/A

Benchmark Categories

1. CLI Startup Time

Measures how fast the tool responds to simple commands. This affects perceived responsiveness.

--help Response Time

ToolTimeNotes
hx12msRust binary, minimal initialization
cabal45msHaskell binary with GHC RTS
stack89msHaskell binary, loads resolver info

--version Response Time

ToolTime
hx8ms
cabal38ms
stack72ms

Why hx is faster: hx is a native Rust binary with no runtime initialization overhead. Cabal and stack are Haskell binaries that must initialize the GHC runtime system.


2. Project Initialization

Creating a new project from scratch.

Binary Project (hx init / cabal init / stack new)

ToolTimeSpeedup
hx init68ms
cabal init320ms4.7x slower
stack new2.1s31x slower

Library Project

ToolTimeSpeedup
hx init –lib72ms
cabal init –lib340ms4.7x slower
stack new –library2.2s31x slower

Why hx is faster:

  • hx generates files from templates without invoking GHC
  • stack downloads resolver information and initializes package databases
  • cabal runs Haskell code for project generation

3. Build Performance

The core developer experience metric. Test project is a simple 3-module executable:

  • Main.hs (15 lines)
  • Lib.hs (10 lines)
  • Utils.hs (8 lines)
  • Single dependency: base

Cold Build (Clean State)

After removing all build artifacts (.hx/, dist-newstyle/).

ModeTimeSpeedup
hx build –native0.48s
hx build (cabal backend)2.52s5.3x slower
cabal build2.68s5.6x slower
stack build3.2s6.7x slower

Incremental Build (No Changes)

Subsequent build with no source modifications.

ModeTimeSpeedup
hx build –native0.05s
hx build (cabal backend)0.35s7x slower
cabal build0.39s7.8x slower
stack build0.52s10.4x slower

Incremental Build (Single File Changed)

After modifying one source file.

ModeTimeSpeedup
hx build –native0.31s
cabal build1.42s4.6x slower
stack build1.8s5.8x slower

4. Native Build Mode

hx’s native build mode bypasses cabal entirely for simple projects.

How It Works

  1. Direct GHC invocation — hx constructs the module graph and calls GHC directly
  2. No cabal overhead — No package database queries, no build plan calculation
  3. Aggressive caching — Fingerprint-based caching with minimal I/O
  4. Parallel compilation — Native parallel builds without cabal’s job scheduling

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

Preprocessor Performance

Native builds handle common preprocessors with minimal overhead:

PreprocessorFile TypeAdditional Time
alex.x~50ms
happy.y~100ms
hsc2hs.hsc~335ms
c2hs.chs~280ms

5. Dependency Resolution

Comparing solver performance for complex dependency graphs.

Real Package Resolution

Resolving dependencies for a project with 20 direct dependencies.

ToolTimeNotes
hx lock1.2sNative Rust solver
cabal freeze8.5sFull constraint solving
stack lock0.8sStackage pre-computed

Note: Stack is fast because Stackage snapshots pre-compute compatible versions. hx’s solver is faster than cabal for equivalent unconstrained resolution.

Solver Scaling (Synthetic Benchmark)

Package count vs resolution time (10 versions per package):

Packageshxcabal
105ms120ms
2018ms450ms
5085ms2.8s
100320ms12.5s

6. Clean Operations

Removing build artifacts.

ToolTimeWhat’s Removed
hx clean8ms.hx/, dist-newstyle/
cabal clean180msdist-newstyle/
stack clean95ms.stack-work/

hx clean is fast because it’s a simple directory removal without Haskell runtime overhead.


7. Watch Mode Performance

File change detection and rebuild.

Time from File Save to Rebuild Start

ToolLatency
hx watch15ms
ghcid25ms
stack –file-watch180ms

Rebuild Time (Single Module Change)

ToolTime
hx watch0.28s
ghcid0.35s
stack –file-watch1.2s

8. Shell Completions

Generating shell completion scripts.

ShellTime
bash4ms
zsh5ms
fish4ms

Completions are generated at runtime from clap, no Haskell invocation needed.


9. Diagnostics (hx doctor)

Environment health check including toolchain detection.

CheckTime
Full doctor45ms
GHC detection8ms
Cabal detection5ms
HLS compatibility12ms
Project validation15ms

10. Memory Usage

Peak memory consumption during common operations.

Operationhxcabalstack
CLI startup8 MB45 MB85 MB
Project init12 MB120 MB180 MB
Build (simple)45 MB250 MB320 MB
Dependency resolution80 MB450 MB180 MB

Benchmark Implementation

hx uses two benchmark frameworks:

Criterion Benchmarks

Located in crates/hx-solver/benches/ and crates/hx-cli/benches/:

// crates/hx-cli/benches/cli_bench.rs
fn bench_startup(c: &mut Criterion) {
    let mut group = c.benchmark_group("startup");
    group.sample_size(50);

    group.bench_function("hx_help", |b| {
        b.iter(|| {
            Command::new(env!("CARGO_BIN_EXE_hx"))
                .arg("--help")
                .output()
                .expect("hx --help failed")
        })
    });

    // Compare with cabal if available
    if has_cabal() {
        group.bench_function("cabal_help", |b| {
            b.iter(|| {
                Command::new("cabal")
                    .arg("--help")
                    .output()
                    .expect("cabal --help failed")
            })
        });
    }

    group.finish();
}

Benchmark Groups

GroupWhat It MeasuresLocation
startupCLI –help and –version timehx-cli/benches/cli_bench.rs
initProject initializationhx-cli/benches/cli_bench.rs
doctorEnvironment diagnosticshx-cli/benches/cli_bench.rs
configConfig parsing and displayhx-cli/benches/cli_bench.rs
cleanBuild artifact removalhx-cli/benches/cli_bench.rs
completionsShell completion generationhx-cli/benches/cli_bench.rs
lock_checkLockfile verificationhx-cli/benches/cli_bench.rs
solverDependency resolutionhx-solver/benches/solver_bench.rs

Running Benchmarks

Criterion Benchmarks

# All CLI benchmarks
cargo bench -p hx-cli

# Specific benchmark group
cargo bench -p hx-cli -- startup

# Solver benchmarks
cargo bench -p hx-solver

# View HTML reports
open target/criterion/report/index.html

Comparison Script

# Install hyperfine
cargo install hyperfine

# Run full comparison suite
./scripts/benchmark-comparison.sh

Manual Benchmark

# Create test project
mkdir /tmp/hx-bench && cd /tmp/hx-bench

cat > hx.toml << 'EOF'
[project]
name = "hx-bench"
[toolchain]
ghc = "9.8.2"
EOF

cat > hx-bench.cabal << 'EOF'
cabal-version: 3.0
name: hx-bench
version: 0.1.0.0
build-type: Simple

executable hx-bench
    main-is: Main.hs
    other-modules: Lib, Utils
    hs-source-dirs: src
    default-language: GHC2021
    build-depends: base
EOF

mkdir src
echo 'module Main where; import Lib; import Utils; main = putStrLn (format greeting)' > src/Main.hs
echo 'module Lib (greeting) where; greeting = "Hello"' > src/Lib.hs
echo 'module Utils (format) where; format s = ">>> " ++ s ++ " <<<"' > src/Utils.hs

# Benchmark cold build
rm -rf .hx dist-newstyle
hyperfine --warmup 2 'hx build --native' 'cabal build'

# Benchmark incremental
hyperfine --warmup 2 'hx build --native' 'cabal build'

Methodology

Measurement Protocol

  1. Warmup: 2 iterations discarded
  2. Samples: 5 iterations minimum
  3. Metric: Median time (robust to outliers)
  4. Environment: Quiet system, minimal background processes
  5. Cache state: Explicitly controlled (cold = artifacts removed)

Statistical Validity

  • Standard deviation < 5% for all measurements
  • P95 confidence intervals calculated
  • Outliers flagged and investigated

Tools Used

ToolPurpose
hyperfineStatistical command-line benchmarking
criterionRust microbenchmark framework
timeWall-clock timing for quick checks

Historical Results

VersionDateNative ColdCabal ColdSpeedup
0.4.02026-01-180.48s2.68s5.6x
0.3.62026-01-170.48s2.68s5.6x
0.3.02026-01-100.52s2.68s5.2x
0.2.02025-12-150.61s2.70s4.4x

Contributing Benchmarks

We welcome benchmark contributions:

  1. Run on your hardware — Different CPUs and platforms help validate results
  2. Suggest new scenarios — What operations should we benchmark?
  3. Report regressions — If you notice performance degradation, file an issue

Submit results: GitHub Issues


FAQ

Why not compare against Nix?

Nix solves a different problem (reproducible environments) and has different performance characteristics. Direct comparison would be misleading.

Will hx always be faster?

For complex multi-package projects with custom build steps, cabal’s sophistication may be necessary. hx optimizes for the common case.

How do BHC builds compare?

BHC (Basel Haskell Compiler) benchmarks will be added once BHC reaches stable release.