Skip to content

ExponentialRK: remove per-step allocations from EPIRK/Exp4/EXPRB53s3 steppers - #3928

Merged
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:exprk-nonallocating-steps
Jul 23, 2026
Merged

ExponentialRK: remove per-step allocations from EPIRK/Exp4/EXPRB53s3 steppers#3928
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:exprk-nonallocating-steps

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member

Summary

The in-place perform_step! implementations of Exp4, EPIRK4s3A/B, EPIRK5s3, EXPRB53s3, and EPIRK5P1/P2 allocated on every step:

  • literal snapshot-time arrays ([dt / 3, 2dt / 3, dt], [g11, g21, g31], …) constructed per step for phiv_timestep! — now a ts vector preallocated in the cache and filled in place;
  • coefficient vectors for 3-column linear combinations built per step (mul!(rtmp, K, [-7 / 300, 97 / 150, -37 / 300])) — now fused broadcasts over the columns of K;
  • unfused broadcast right-hand sides that materialized N-sized temporaries (B[:, 4] .= (32 / dt^2) * rtmp and friends, plus two broadcast-plus-broadcast sums) — now fully fused. These scale with the PDE size, so they matter most exactly where the EPIRK methods are supposed to shine.

Out-of-place (ConstantCache) paths are untouched.

Context / measurements

This is the OrdinaryDiffEq side of an allocation audit of the exponential integrators; the dominant per-step allocations were in ExponentialUtilities (SciML/ExponentialUtilities.jl#254PhivCache reallocated on every phiv! call — and SciML/ExponentialUtilities.jl#255exponential! workspace reuse). Combined per-step allocations on the Kuramoto–Sivashinsky pseudospectral benchmark problem (N=128, non-allocating nonlinearity, fixed dt, Julia 1.12.6):

method released (ExpUtils v1.33.0) + ExpUtils PRs + this PR
EPIRK4s3A 636 KB/step 193 KB 11 KB
EPIRK4s3B 660 KB 201 KB 14 KB
EPIRK5P1 967 KB 190 KB 15 KB
EPIRK5P2 981 KB 279 KB 16 KB
EXPRB53s3 1031 KB 292 KB 18 KB
Exp4 1180 KB 19 KB* 23 KB*

(*Exp4's remaining KB moved between categories as the two changes landed; the end state is equivalent.) Caching-mode ETDRK2/3/4, HochOst4, NorsettEuler, LawsonEuler, ETD2 were verified 0 bytes/step already. The remaining ~10–20 KB/step comes from small per-call allocations inside phiv_timestep! (coeffs = ones(p), scalar-t [t] wrappers) and the finite-difference Jacobian.

Solutions are identical before/after (endpoint norms bit-identical on the KS benchmark).

Tests

Ran ODEDIFFEQ_TEST_GROUP=Core Pkg.test() locally on the sublibrary with the dev'd ExponentialUtilities fixes: Krylov tests + Linear-Nonlinear Convergence Tests pass (40 pass, 2 pre-existing @test_broken). Runic formatting applied.

Version bumped 2.1.0 → 2.1.1 (internal cache structs only, no public API change).


Note: please ignore this PR until reviewed by @ChrisRackauckas.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EDVGmmovzD5Aos3fFoPwMY

…steppers

The in-place perform_step! implementations of Exp4, EPIRK4s3A/B,
EPIRK5s3, EXPRB53s3, and EPIRK5P1/P2 allocated every step:

- literal snapshot-time arrays ([dt/3, 2dt/3, dt], [g11, g21, g31], ...)
  constructed per step for phiv_timestep!; now a ts vector preallocated
  in the cache and filled in place
- coefficient vectors for 3-column linear combinations built per step
  (mul!(rtmp, K, [-7/300, 97/150, -37/300])); now fused broadcasts over
  the columns of K
- unfused broadcast right-hand sides that materialized N-sized
  temporaries (B[:, 4] .= (32 / dt^2) * rtmp and friends, and two
  broadcast-plus-broadcast sums); now fully fused with dots

The N-sized temporaries in particular scale with the PDE size. Together
with ExponentialUtilities#254/SciML#255 this takes per-step allocations on
the Kuramoto-Sivashinsky pseudospectral benchmark (N=128, non-allocating
nonlinearity) from 0.6-1.2 MB/step to 11-23 KB/step for the EPIRK
family; solutions are identical.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas
ChrisRackauckas marked this pull request as ready for review July 23, 2026 11:59
@ChrisRackauckas
ChrisRackauckas merged commit d2577da into SciML:master Jul 23, 2026
113 of 129 checks passed
ChrisRackauckas added a commit that referenced this pull request Jul 23, 2026
…-step allocations (#3993)

The EPIRK/Exp4/Exprb steppers still allocated per step through bare
column-slice compound assignments of the form `X[:, i] .op= ...`
(e.g. `K[:, i] ./= ts[i]`, `B[:, 4] .-= c .* rtmp`). Without `@views`
the right-hand-side read of the slice materializes a fresh column copy
each time (Base `_unsafe_getindex -> similar`), so each such line
allocates one working vector per step.

#3928 converted the large fused updates to `@views @.. broadcast=false`
but left these smaller normalization/update slices unviewed. Wrapping
them in `@views` makes the read a view instead of a copy; the operation
is still an in-place elementwise write to the same memory, so the result
is unchanged (each element reads only its own position -- no aliasing).

Measured per-step allocations on a 1D semilinear reaction-diffusion
problem (N=128, adaptive_krylov, m=30), via the `step!` interface after
warmup, master vs this branch:

  Exp4       7647 -> 927 B/step  (8.2x)
  EPIRK4s3B  5132 -> 652 B/step  (7.9x)
  EPIRK4s3A  2879 -> 639 B/step  (4.5x)
  EXPRB53s3  3180 -> 940 B/step  (3.4x)
  EPIRK5P1   2047 -> 927 B/step  (2.2x)

Final solution is bit-identical before/after for every method
(max|Δu| = 0 across Exp4/EPIRK4s3A/EPIRK4s3B/EXPRB53s3/EPIRK5P1/EPIRK5P2/
Exprb32/Exprb43). The remaining ~0.6-0.9 KB/step floor is in the
ExponentialUtilities adaptive-Krylov path (a `v[1:end-1]` copy in
`lanczos!` and a reshaped-subarray `similar`), tracked separately.

Adds a runtime size-independence regression test (Exp4 on a reaction-
diffusion problem): per-step allocations must not scale with the state
size, which cleanly catches a reintroduced unviewed slice copy without a
flaky absolute byte ceiling.

Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants