ExponentialRK: @views residual column-slice updates to remove per-step allocations - #3993
Merged
ChrisRackauckas merged 1 commit intoJul 23, 2026
Conversation
…-step allocations 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. SciML#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: Chris Rackauckas <accounts@chrisrackauckas.com>
ChrisRackauckas
marked this pull request as ready for review
July 23, 2026 13:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #3928. 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, theright-hand-side read of the slice materializes a fresh length-
ncolumn copyeach time (Base
_unsafe_getindex -> similar), so each such line allocates oneworking vector per step and the cost grows linearly with the state size.
#3928 converted the large fused updates to
@views @.. broadcast=falsebutleft these smaller normalization/update slices unviewed. This wraps the
remaining 17 of them in
@views. Each is an in-place elementwise write back tothe same slice with a scalar or separate-array RHS (no column aliasing), so the
result is unchanged — only the transient copy is removed.
Measured effect
Per-step allocations via the
step!interface after warmup, on a 1D semilinearreaction–diffusion problem (
N=128,adaptive_krylov,m=30), master vs thisbranch:
The final solution is bit-identical before/after for every method
(
max|Δu| = 0across Exp4/EPIRK4s3A/EPIRK4s3B/EXPRB53s3/EPIRK5P1/EPIRK5P2/Exprb32/Exprb43).
Test
Adds a runtime size-independence regression test:
Exp4's per-step allocationmust not scale with the state size
n(measured atn=32vsn=256). Exp4uses the symmetric-Jacobian Lanczos path with a stable Krylov subspace, so once
the slices are views its per-step allocation is
n-independent; a reintroducedslice copy makes it scale with
nand trips the test. This is version- andplatform-robust, unlike an absolute byte ceiling.
Verified locally: the
Coregroup (Linear-Nonlinear Krylov + Convergencetests) passes; the new regression test passes (ratio ≈ 1.0 on this branch, ≈
4.8 on master). The 3 pre-existing
Aqua/QA failures (missing EPIRK docstringsRemaining work (separate PR)
At large
n, the EPIRK family still allocates in the ExponentialUtilitiesadaptive
phiv_timestep!path — the matrix-exponential Padé workspace isreallocated (
alloc_mem) when the adaptive subspace size varies (cache keyed bysize → misses), and the Krylov subspace is
resize!d. That is a separateExponentialUtilities change and is not addressed here.
Draft — please ignore until reviewed by @ChrisRackauckas.
🤖 Generated with Claude Code