Benchmarks
LoweredDistributions benchmarks its lowering hot paths to track performance over time.
What the suite covers
Lowering: the adaptive
lowerdispatch on both sides of thec²split (anErlangChainfit and aPhaseTypehyperexponential fit), and the type-stable canonicallower(dist, PhaseType)form, including the AD-stable fixed-phase-count path.Evaluation: the
ctmcbuilder combined withtransition_probability's matrix exponential, and the phase-type survival read directly off a canonical(α, S)— this package's closest analogue to alogpdf-style scoring hot path, since every representation canonicalises to a phase-type before it is scored.Bridges: turning a lowering into a framework-native problem — Catalyst's
reaction_system, JumpProcesses'jump_problem, and SciMLBase'sode_problem— timed as construction only, not simulation.AD gradients: the same scenarios
test/ad/runtests.jlexercises, timed per AD backend (ForwardDiff, ReverseDiff, Mooncake, Enzyme). A (scenario, backend) pair is only benchmarked once its gradient is confirmed finite, so a backend that cannot yet handle a scenario is silently omitted rather than failing the run.
Running the suite locally
julia --project=benchmark benchmark/run.jl results.jsonOr compare the working tree against another revision with AirspeedVelocity's benchpkg:
benchpkg --rev=main,dirty --script=benchmark/benchmarks.jlFramework-specialisation check
The lowering bridges above are framework-agnostic: reaction_system builds its Catalyst reactions off the same generic (α, S) phase-type walk regardless of which distribution produced it, rather than special-casing an Erlang chain. The block below checks that genericity is not a performance problem, by comparing an Erlang passage time built two ways: a hand-written Catalyst reaction chain (the framework-native form) against reaction_system applied to lower's ErlangChain fit of the equivalent Gamma. It runs live at docs-build time (a small Chairmarks budget per point), so the numbers below always reflect the current lowering code, not a pasted snapshot.
using Catalyst, LoweredDistributions, Distributions, OrdinaryDiffEqTsit5
using Chairmarks
const RATE = 1.0
t = Catalyst.default_t()
Catalyst.@species Src(t) Snk(t)
function native_chain(k)
species = [(Catalyst.@species $(Symbol(:S, i))(t))[1] for i in 1:k]
rxs = Reaction[Reaction(RATE, [Src], [species[1]])]
for i in 1:(k - 1)
push!(rxs, Reaction(RATE, [species[i]], [species[i + 1]]))
end
push!(rxs, Reaction(RATE, [species[k]], [Snk]))
return ReactionSystem(rxs, t; name = :native)
end
lowered_chain(k) = reaction_system(Gamma(k, 1 / RATE), Src, Snk)
function to_odeprob(rn)
rn = complete(rn)
u0 = [s => (isequal(s, Src) ? 1.0 : 0.0) for s in species(rn)]
return ODEProblem(rn, u0, (0.0, 10.0))
end
for k in (3, 10, 25)
b_construct_native = @be native_chain($k) seconds=0.5
b_construct_lowered = @be lowered_chain($k) seconds=0.5
prob_native = to_odeprob(native_chain(k))
prob_lowered = to_odeprob(lowered_chain(k))
b_solve_native = @be solve($prob_native, Tsit5()) seconds=0.5
b_solve_lowered = @be solve($prob_lowered, Tsit5()) seconds=0.5
construct_ratio = minimum(b_construct_lowered).time /
minimum(b_construct_native).time
solve_ratio = minimum(b_solve_lowered).time / minimum(b_solve_native).time
println("k = ", k, ": construction ratio (lowered/native) = ",
round(construct_ratio; digits = 3), ", solve ratio = ",
round(solve_ratio; digits = 3))
endk = 3: construction ratio (lowered/native) = 1.015, solve ratio = 1.023
k = 10: construction ratio (lowered/native) = 0.987, solve ratio = 1.007
k = 25: construction ratio (lowered/native) = 1.014, solve ratio = 1.009A ratio near 1 means the generic path costs about the same as writing the chain by hand. Construction runs a flat few percent slower — the extra allocations from the generic phase-species-building loop — and does not grow with the phase count; the ODE solve is at parity, since by the time the system reaches the solver both paths have produced the same list of reactions.
Reading the history
The plot and table below are generated by the benchmark-history CI job on every push to main and on tagged releases, and published to the repo's benchmarks branch. Each point is a full run of the suite above at that revision; a rising line in a timing panel is a regression, a falling one an improvement.
Performance history
The summary tracks each benchmark suite's headline timing across recent revisions.
Benchmark summary (overall)
Not enough comparable revisions to compute ratios yet — the summary populates once a second revision is benchmarked.
<details> <summary>Per-suite detail</summary>
Ratio summary
Most recent 1 revision, columns labelled by commit date.
AD gradients
Time
| Benchmark | 9afc6521b0c198... |
|---|---|
| ctmc(specs...) builder + transition_probability gradient/ForwardDiff | 22.2 ± 7.7 μs |
| ctmc(specs...) builder + transition_probability gradient/Mooncake forward | 0.25 ± 0.02 ms |
| ctmc(specs...) builder + transition_probability gradient/Mooncake reverse | 1.26 ± 0.049 ms |
| ctmc(specs...) builder + transition_probability gradient/ReverseDiff (tape) | 0.804 ± 0.1 ms |
| lower(composer) joint-CTMC transition gradient/Enzyme forward | 0.0967 ± 0.0059 ms |
| lower(composer) joint-CTMC transition gradient/Enzyme reverse | 0.415 ± 0.039 ms |
| lower(composer) joint-CTMC transition gradient/ForwardDiff | 0.0432 ± 0.0038 ms |
| lower(composer) joint-CTMC transition gradient/Mooncake forward | 0.23 ± 0.017 ms |
| lower(composer) joint-CTMC transition gradient/Mooncake reverse | 1.91 ± 0.2 ms |
| lower(composer) joint-CTMC transition gradient/ReverseDiff (tape) | 2.73 ± 0.35 ms |
| lower(composer) scalar-composer survival gradient/Enzyme forward | 0.0475 ± 0.0056 ms |
| lower(composer) scalar-composer survival gradient/Enzyme reverse | 0.553 ± 0.04 ms |
| lower(composer) scalar-composer survival gradient/ForwardDiff | 20.5 ± 4.7 μs |
| lower(composer) scalar-composer survival gradient/Mooncake forward | 0.127 ± 0.02 ms |
| lower(composer) scalar-composer survival gradient/Mooncake reverse | 1.58 ± 0.079 ms |
| lower(composer) scalar-composer survival gradient/ReverseDiff (tape) | 0.82 ± 0.099 ms |
| lower(dist) adaptive Erlang survival gradient (integer shape)/Enzyme forward | 10.5 ± 3.8 μs |
| lower(dist) adaptive Erlang survival gradient (integer shape)/Enzyme reverse | 0.254 ± 0.023 ms |
| lower(dist) adaptive Erlang survival gradient (integer shape)/ForwardDiff | 7.75 ± 0.78 μs |
| lower(dist) adaptive Erlang survival gradient (integer shape)/Mooncake forward | 0.0381 ± 0.011 ms |
| lower(dist) adaptive Erlang survival gradient (integer shape)/Mooncake reverse | 0.47 ± 0.12 ms |
| lower(dist) adaptive Erlang survival gradient (integer shape)/ReverseDiff (tape) | 0.789 ± 0.096 ms |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/Enzyme forward | 9.51 ± 0.96 μs |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/Enzyme reverse | 0.241 ± 0.026 ms |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/ForwardDiff | 4.69 ± 0.84 μs |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/Mooncake forward | 25.8 ± 2.2 μs |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/Mooncake reverse | 0.338 ± 0.036 ms |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/ReverseDiff (tape) | 0.299 ± 0.041 ms |
| lower(dist) adaptive-dispatch survival gradient/Enzyme forward | 9.67 ± 1.2 μs |
| lower(dist) adaptive-dispatch survival gradient/Enzyme reverse | 0.236 ± 0.021 ms |
| lower(dist) adaptive-dispatch survival gradient/ForwardDiff | 5.06 ± 1.1 μs |
| lower(dist) adaptive-dispatch survival gradient/Mooncake forward | 26.5 ± 3.8 μs |
| lower(dist) adaptive-dispatch survival gradient/Mooncake reverse | 0.303 ± 0.028 ms |
| lower(dist) adaptive-dispatch survival gradient/ReverseDiff (tape) | 0.335 ± 0.051 ms |
| lower(dist, PhaseType) survival gradient (c² > 1)/Enzyme forward | 9.71 ± 1.8 μs |
| lower(dist, PhaseType) survival gradient (c² > 1)/Enzyme reverse | 0.241 ± 0.028 ms |
| lower(dist, PhaseType) survival gradient (c² > 1)/ForwardDiff | 5.16 ± 0.97 μs |
| lower(dist, PhaseType) survival gradient (c² > 1)/Mooncake forward | 27.1 ± 3.5 μs |
| lower(dist, PhaseType) survival gradient (c² > 1)/Mooncake reverse | 0.314 ± 0.029 ms |
| lower(dist, PhaseType) survival gradient (c² > 1)/ReverseDiff (tape) | 0.341 ± 0.051 ms |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/Enzyme forward | 10.2 ± 3.6 μs |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/Enzyme reverse | 0.242 ± 0.028 ms |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/ForwardDiff | 7.43 ± 0.74 μs |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/Mooncake forward | 0.0379 ± 0.012 ms |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/Mooncake reverse | 0.475 ± 0.05 ms |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/ReverseDiff (tape) | 0.794 ± 0.096 ms |
| lower(dist, PhaseType; phases) fixed-count survival gradient/Enzyme forward | 27 ± 2.8 μs |
| lower(dist, PhaseType; phases) fixed-count survival gradient/Enzyme reverse | 0.121 ± 0.012 ms |
| lower(dist, PhaseType; phases) fixed-count survival gradient/ForwardDiff | 16.1 ± 0.79 μs |
| lower(dist, PhaseType; phases) fixed-count survival gradient/Mooncake forward | 0.0649 ± 0.0046 ms |
| lower(dist, PhaseType; phases) fixed-count survival gradient/Mooncake reverse | 0.351 ± 0.042 ms |
| lower(dist, PhaseType; phases) fixed-count survival gradient/ReverseDiff (tape) | 2.9 ± 0.35 ms |
| matrix_exp/transition_probability direct gradient/Enzyme forward | 24.5 ± 10 μs |
| matrix_exp/transition_probability direct gradient/Enzyme reverse | 0.25 ± 0.031 ms |
| matrix_exp/transition_probability direct gradient/ForwardDiff | 13.1 ± 0.54 μs |
| matrix_exp/transition_probability direct gradient/Mooncake forward | 0.0698 ± 0.0085 ms |
| matrix_exp/transition_probability direct gradient/Mooncake reverse | 0.428 ± 0.12 ms |
| matrix_exp/transition_probability direct gradient/ReverseDiff (tape) | 0.78 ± 0.098 ms |
| ode_problem solve survival gradient (PhaseType)/Enzyme forward | 16.9 ± 0.68 ms |
| ode_problem solve survival gradient (PhaseType)/ForwardDiff | 0.29 ± 0.013 ms |
| ode_problem solve survival gradient (PhaseType, direct)/Enzyme forward | 17 ± 0.58 ms |
| ode_problem solve survival gradient (PhaseType, direct)/ForwardDiff | 0.296 ± 0.014 ms |
| phase_type hyperexponential (α, S) gradient/Enzyme forward | 6.19 ± 0.072 μs |
| phase_type hyperexponential (α, S) gradient/Enzyme reverse | 1.32 ± 0.035 μs |
| phase_type hyperexponential (α, S) gradient/ForwardDiff | 0.491 ± 0.051 μs |
| phase_type hyperexponential (α, S) gradient/Mooncake forward | 11.4 ± 0.56 μs |
| phase_type hyperexponential (α, S) gradient/Mooncake reverse | 14.7 ± 0.73 μs |
| phase_type hyperexponential (α, S) gradient/ReverseDiff (tape) | 8.06 ± 0.21 μs |
Memory
| Benchmark | 9afc6521b0c198... |
|---|---|
| ctmc(specs...) builder + transition_probability gradient/ForwardDiff | 0.22 k allocs: 26.3 kB |
| ctmc(specs...) builder + transition_probability gradient/Mooncake forward | 2.8 k allocs: 0.123 MB |
| ctmc(specs...) builder + transition_probability gradient/Mooncake reverse | 8.45 k allocs: 0.79 MB |
| ctmc(specs...) builder + transition_probability gradient/ReverseDiff (tape) | 8.96 k allocs: 0.371 MB |
| lower(composer) joint-CTMC transition gradient/Enzyme forward | 0.8 k allocs: 0.0514 MB |
| lower(composer) joint-CTMC transition gradient/Enzyme reverse | 2 k allocs: 0.196 MB |
| lower(composer) joint-CTMC transition gradient/ForwardDiff | 0.408 k allocs: 0.0391 MB |
| lower(composer) joint-CTMC transition gradient/Mooncake forward | 1.9 k allocs: 0.119 MB |
| lower(composer) joint-CTMC transition gradient/Mooncake reverse | 11.8 k allocs: 1.07 MB |
| lower(composer) joint-CTMC transition gradient/ReverseDiff (tape) | 0.0318 M allocs: 1.36 MB |
| lower(composer) scalar-composer survival gradient/Enzyme forward | 0.595 k allocs: 28.8 kB |
| lower(composer) scalar-composer survival gradient/Enzyme reverse | 1.47 k allocs: 0.111 MB |
| lower(composer) scalar-composer survival gradient/ForwardDiff | 0.305 k allocs: 20.8 kB |
| lower(composer) scalar-composer survival gradient/Mooncake forward | 1.44 k allocs: 0.0697 MB |
| lower(composer) scalar-composer survival gradient/Mooncake reverse | 10.9 k allocs: 0.984 MB |
| lower(composer) scalar-composer survival gradient/ReverseDiff (tape) | 9.19 k allocs: 0.379 MB |
| lower(dist) adaptive Erlang survival gradient (integer shape)/Enzyme forward | 0.259 k allocs: 16.7 kB |
| lower(dist) adaptive Erlang survival gradient (integer shape)/Enzyme reverse | 0.768 k allocs: 0.0801 MB |
| lower(dist) adaptive Erlang survival gradient (integer shape)/ForwardDiff | 0.124 k allocs: 12.5 kB |
| lower(dist) adaptive Erlang survival gradient (integer shape)/Mooncake forward | 0.586 k allocs: 0.0374 MB |
| lower(dist) adaptive Erlang survival gradient (integer shape)/Mooncake reverse | 3.29 k allocs: 0.58 MB |
| lower(dist) adaptive Erlang survival gradient (integer shape)/ReverseDiff (tape) | 8.89 k allocs: 0.368 MB |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/Enzyme forward | 0.247 k allocs: 12.7 kB |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/Enzyme reverse | 0.708 k allocs: 0.0726 MB |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/ForwardDiff | 0.118 k allocs: 7.86 kB |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/Mooncake forward | 0.562 k allocs: 30.4 kB |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/Mooncake reverse | 2.73 k allocs: 0.279 MB |
| lower(dist) adaptive Erlang survival gradient (non-integer shape)/ReverseDiff (tape) | 3.25 k allocs: 0.137 MB |
| lower(dist) adaptive-dispatch survival gradient/Enzyme forward | 0.261 k allocs: 13.7 kB |
| lower(dist) adaptive-dispatch survival gradient/Enzyme reverse | 0.647 k allocs: 0.0714 MB |
| lower(dist) adaptive-dispatch survival gradient/ForwardDiff | 0.125 k allocs: 8.48 kB |
| lower(dist) adaptive-dispatch survival gradient/Mooncake forward | 0.588 k allocs: 0.0315 MB |
| lower(dist) adaptive-dispatch survival gradient/Mooncake reverse | 2.51 k allocs: 0.273 MB |
| lower(dist) adaptive-dispatch survival gradient/ReverseDiff (tape) | 3.75 k allocs: 0.154 MB |
| lower(dist, PhaseType) survival gradient (c² > 1)/Enzyme forward | 0.261 k allocs: 13.7 kB |
| lower(dist, PhaseType) survival gradient (c² > 1)/Enzyme reverse | 0.651 k allocs: 0.0715 MB |
| lower(dist, PhaseType) survival gradient (c² > 1)/ForwardDiff | 0.125 k allocs: 8.48 kB |
| lower(dist, PhaseType) survival gradient (c² > 1)/Mooncake forward | 0.588 k allocs: 0.0315 MB |
| lower(dist, PhaseType) survival gradient (c² > 1)/Mooncake reverse | 2.56 k allocs: 0.275 MB |
| lower(dist, PhaseType) survival gradient (c² > 1)/ReverseDiff (tape) | 3.78 k allocs: 0.156 MB |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/Enzyme forward | 0.245 k allocs: 16.1 kB |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/Enzyme reverse | 0.631 k allocs: 0.0727 MB |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/ForwardDiff | 0.117 k allocs: 12.1 kB |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/Mooncake forward | 0.556 k allocs: 0.0363 MB |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/Mooncake reverse | 3.12 k allocs: 0.571 MB |
| lower(dist, PhaseType) survival gradient (c² ≤ 1)/ReverseDiff (tape) | 8.87 k allocs: 0.367 MB |
| lower(dist, PhaseType; phases) fixed-count survival gradient/Enzyme forward | 0.241 k allocs: 28.9 kB |
| lower(dist, PhaseType; phases) fixed-count survival gradient/Enzyme reverse | 0.621 k allocs: 0.0874 MB |
| lower(dist, PhaseType; phases) fixed-count survival gradient/ForwardDiff | 0.115 k allocs: 25.8 kB |
| lower(dist, PhaseType; phases) fixed-count survival gradient/Mooncake forward | 0.548 k allocs: 0.0613 MB |
| lower(dist, PhaseType; phases) fixed-count survival gradient/Mooncake reverse | 1.87 k allocs: 0.358 MB |
| lower(dist, PhaseType; phases) fixed-count survival gradient/ReverseDiff (tape) | 0.033 M allocs: 1.41 MB |
| matrix_exp/transition_probability direct gradient/Enzyme forward | 0.496 k allocs: 0.0319 MB |
| matrix_exp/transition_probability direct gradient/Enzyme reverse | 0.65 k allocs: 0.0721 MB |
| matrix_exp/transition_probability direct gradient/ForwardDiff | 0.123 k allocs: 21.7 kB |
| matrix_exp/transition_probability direct gradient/Mooncake forward | 1.05 k allocs: 0.0694 MB |
| matrix_exp/transition_probability direct gradient/Mooncake reverse | 2.99 k allocs: 0.563 MB |
| matrix_exp/transition_probability direct gradient/ReverseDiff (tape) | 8.86 k allocs: 0.367 MB |
| ode_problem solve survival gradient (PhaseType)/Enzyme forward | 0.0549 M allocs: 2.35 MB |
| ode_problem solve survival gradient (PhaseType)/ForwardDiff | 0.666 k allocs: 0.0489 MB |
| ode_problem solve survival gradient (PhaseType, direct)/Enzyme forward | 0.0549 M allocs: 2.35 MB |
| ode_problem solve survival gradient (PhaseType, direct)/ForwardDiff | 0.674 k allocs: 0.0493 MB |
| phase_type hyperexponential (α, S) gradient/Enzyme forward | 29 allocs: 1.06 kB |
| phase_type hyperexponential (α, S) gradient/Enzyme reverse | 18 allocs: 0.656 kB |
| phase_type hyperexponential (α, S) gradient/ForwardDiff | 9 allocs: 0.406 kB |
| phase_type hyperexponential (α, S) gradient/Mooncake forward | 0.124 k allocs: 7.08 kB |
| phase_type hyperexponential (α, S) gradient/Mooncake reverse | 0.204 k allocs: 10.3 kB |
| phase_type hyperexponential (α, S) gradient/ReverseDiff (tape) | 0.123 k allocs: 4.84 kB |
Bridges
Time
| Benchmark | 9afc6521b0c198... |
|---|---|
| jump_problem (JumpProcesses) | 1.22 ± 0.079 μs |
| ode_problem (SciMLBase) | 0.166 ± 0.0052 ms |
| reaction_system (Catalyst) | 0.0742 ± 0.0032 ms |
Memory
| Benchmark | 9afc6521b0c198... |
|---|---|
| jump_problem (JumpProcesses) | 0.052 k allocs: 2.38 kB |
| ode_problem (SciMLBase) | 0.596 k allocs: 0.0446 MB |
| reaction_system (Catalyst) | 0.68 k allocs: 25.7 kB |
Evaluation
Time
| Benchmark | 9afc6521b0c198... |
|---|---|
| ctmc builder + transition_probability | 4.77 ± 0.97 μs |
| phase-type matrix_exp | 3.8 ± 2.5 μs |
Memory
| Benchmark | 9afc6521b0c198... |
|---|---|
| ctmc builder + transition_probability | 0.11 k allocs: 7.69 kB |
| phase-type matrix_exp | 0.114 k allocs: 6.22 kB |
Lowering
Time
| Benchmark | 9afc6521b0c198... |
|---|---|
| canonical(Erlang branch) | 0.148 ± 0.038 μs |
| canonical(PhaseType branch) | 0.126 ± 0.059 μs |
| canonical(fixed phases) | 0.195 ± 0.028 μs |
| lower(Exponential) | 1.98 ± 0.064 μs |
| lower(Gamma, Erlang branch) | 0.0469 ± 0.025 μs |
| lower(Gamma, PhaseType branch) | 0.126 ± 0.061 μs |
Memory
| Benchmark | 9afc6521b0c198... |
|---|---|
| canonical(Erlang branch) | 4 allocs: 0.219 kB |
| canonical(PhaseType branch) | 4 allocs: 0.188 kB |
| canonical(fixed phases) | 4 allocs: 0.359 kB |
| lower(Exponential) | 15 allocs: 0.797 kB |
| lower(Gamma, Erlang branch) | 3 allocs: 0.0938 kB |
| lower(Gamma, PhaseType branch) | 5 allocs: 0.219 kB |
time_to_load
Time
| Benchmark | 9afc6521b0c198... |
|---|---|
| time_to_load | 0.509 ± 0.0023 s |
Memory
| Benchmark | 9afc6521b0c198... |
|---|---|
| time_to_load | 0.149 k allocs: 11.2 kB |
Per-benchmark timelines
<details> <summary>Show 4 plots</summary>




</details>
</details>