Demonstrates how monomorphizing a function that carries a large All
constraint can dramatically speed up GHC compilation (~20x).
When a multi-param class function like:
toFormula :: (ToFormula sig l, All (ToFormula sig) sig) => Node sig l -> Maybe Stringis called at many sites (each with a different concrete l), GHC must
re-solve All (ToFormula sig) sig at every call site. With a
130-element type-level list, this means enumerating and verifying 130
multi-param-class instances each time. With 130 call sites, that's
130 x 130 = 16,900 instance resolutions — and GHC's constraint solver
does not cache these across call sites within a module.
A monomorphic wrapper fixes sig and discharges the All constraint once:
monoToFormula :: ToFormula BigSig l => Node BigSig l -> Maybe String
monoToFormula = toFormulaNow callers only need ToFormula BigSig l — a single instance lookup.
GHC solves the expensive 130-element All constraint exactly once at
monoToFormula's definition site.
Caller.hs uses a CPP macro for easy benchmarking:
#ifdef MONOMORPHIZE
#define FOO monoToFormula
#else
#define FOO toFormula
#endifCompile with -DMONOMORPHIZE for the fast version, without for the slow version.
| Version | Total time | GC bytes copied | Speedup |
|---|---|---|---|
| Polymorphic | 2.61s | 2,571 MB | 1x |
| Monomorphized | 0.12s | 89 MB | ~21x |
# From a devenv shell (or any env with GHC 9.8+):
./bench.sh