Skip to content

ExponentialRK: @views residual column-slice updates to remove per-step allocations - #3993

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

ExponentialRK: @views residual column-slice updates to remove per-step allocations#3993
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:exprk-view-slices

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member

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, the
right-hand-side read of the slice materializes a fresh length-n column copy
each time (Base _unsafe_getindex -> similar), so each such line allocates one
working vector per step and the cost grows linearly with the state size.

#3928 converted the large fused updates to @views @.. broadcast=false but
left these smaller normalization/update slices unviewed. This wraps the
remaining 17 of them in @views. Each is an in-place elementwise write back to
the 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 semilinear
reaction–diffusion problem (N=128, adaptive_krylov, m=30), master vs this
branch:

method master this PR reduction
Exp4 7647 B 927 B 8.2×
EPIRK4s3B 5132 B 652 B 7.9×
EPIRK4s3A 2879 B 639 B 4.5×
EXPRB53s3 3180 B 940 B 3.4×
EPIRK5P1 2047 B 927 B 2.2×

The final solution is bit-identical before/after for every method
(max|Δu| = 0 across Exp4/EPIRK4s3A/EPIRK4s3B/EXPRB53s3/EPIRK5P1/EPIRK5P2/
Exprb32/Exprb43).

Test

Adds a runtime size-independence regression test: Exp4's per-step allocation
must not scale with the state size n (measured at n=32 vs n=256). Exp4
uses the symmetric-Jacobian Lanczos path with a stable Krylov subspace, so once
the slices are views its per-step allocation is n-independent; a reintroduced
slice copy makes it scale with n and trips the test. This is version- and
platform-robust, unlike an absolute byte ceiling.

Verified locally: the Core group (Linear-Nonlinear Krylov + Convergence
tests) 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 docstrings

  • reexport hygiene) are unrelated to this change and already fail on master.

Remaining work (separate PR)

At large n, the EPIRK family still allocates in the ExponentialUtilities
adaptive phiv_timestep! path — the matrix-exponential Padé workspace is
reallocated (alloc_mem) when the adaptive subspace size varies (cache keyed by
size → misses), and the Krylov subspace is resize!d. That is a separate
ExponentialUtilities change and is not addressed here.


Draft — please ignore until reviewed by @ChrisRackauckas.

🤖 Generated with Claude Code

…-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
ChrisRackauckas marked this pull request as ready for review July 23, 2026 13:17
@ChrisRackauckas
ChrisRackauckas merged commit bf3ad94 into SciML:master Jul 23, 2026
112 of 130 checks passed
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