Benchmarks

Performance benchmarks comparing hx to cabal and stack across common operations

Performance Benchmarks

hx is designed for speed. These benchmarks compare hx against cabal and stack across common development operations.

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

1. CLI Startup Time

How fast does the tool respond to simple commands?

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

Files Created

ToolFilesDirectories
hx6 files2 dirs
cabal4 files1 dir
stack12 files4 dirs

hx creates a complete project with .gitignore, .editorconfig, and proper toolchain pinning.


3. Build Performance

The core developer experience metric.

Test Project

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.

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 Deep Dive

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:

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

Methodology

Benchmarking Tools

  • hyperfine — Statistical command-line benchmarking
  • criterion — Rust microbenchmark framework (for solver benchmarks)
  • time — Wall-clock timing for quick checks

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

Reproducing These Benchmarks

Quick Reproduction

# Install hyperfine
cargo install hyperfine

# Clone hx and run benchmark suite
git clone https://github.com/arcanist-sh/hx.git
cd hx
./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'

Criterion Benchmarks (Solver)

cd hx
cargo bench -p hx-solver
# Results in target/criterion/

CLI Benchmarks

cd hx
cargo bench -p hx-cli
# Results in target/criterion/

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 and submit results
  2. Suggest new benchmark scenarios
  3. Report unexpected performance regressions

Submit results or suggestions: 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.