Fix allocation issues in ComposedOperator by using @generated functions #316
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
Fixes #315 by eliminating GC-triggering allocations in ComposedOperator when using operators as prototypes for W or Jacobian in OrdinaryDiffEq.jl.
Problem
The issue identified three functions in
ComposedOperatorthat were creating tuples dynamically at runtime using splat operations:vecs = (w, L.cache[1:(end - 1)]..., v)inmul!and operator call functionsvecs = (v, reverse(L.cache[1:(end - 1)])..., w)inldiv!These splatting operations allocated memory on every call, triggering frequent GC and causing performance regressions when using SciMLOperators with OrdinaryDiffEq.jl.
Solution
Converted the following functions to use
@generatedfunctions:LinearAlgebra.mul!(w, L::ComposedOperator, v)(src/basic.jl:890-919)LinearAlgebra.ldiv!(w, L::ComposedOperator, v)(src/basic.jl:937-967)(L::ComposedOperator)(w, v, u, p, t; kwargs...)- the in-place operator call (src/basic.jl:987-1016)The
@generatedversions generate specialized code at compile time that directly references cache elements without creating intermediate allocations. This follows the same pattern established forAddedOperator(src/basic.jl:603-630, 639-673).Implementation Details
For each function, the generated code unrolls the loop and directly accesses tuple elements:
mul!: applies operators in reverse order, storing intermediate results in cacheldiv!: applies operators in forward order with reversed cache indexingmul!but using operator call syntaxTesting
Performance Impact
This change should eliminate the allocation hotspot identified in the profiling output shown in issue #315, significantly improving performance when using ComposedOperators in ODE solvers.
🤖 Generated with Claude Code