Skip to content

Use hasmethod for checking mul! implementation#406

Merged
amontoison merged 4 commits intoJuliaSmoothOptimizers:mainfrom
timholy:teh/args5
Feb 22, 2026
Merged

Use hasmethod for checking mul! implementation#406
amontoison merged 4 commits intoJuliaSmoothOptimizers:mainfrom
timholy:teh/args5

Conversation

@timholy
Copy link
Copy Markdown
Contributor

@timholy timholy commented Feb 22, 2026

The LinearOperator constructor was insanely slow (~5μs) because of the call to get_nargs. This removes that call and much of the 5-arg logic, replacing with the use of hasmethod. Because all the types passed to hasmethod are concrete, Julia's compiler can decide this at compile time, so there is no actual runtime cost to this check.

This also removes the internal storage for has_args5 since we can't compute that at construction time without making the constructor very slow. One consequence is a limited test breakage: has_args5 now refers to the "surface-level" operator only, and formerly some composite operations (e.g., hcat, +, etc) would "lie" about whether there was a 5-arg mul! available for the composite object in favor of reporting whether that was true for all the constituents. With this change, has_args5 will report whether the composite object itself has a 5-arg mul! available, which in cases here is "yes." But a few tests were checking that the composite object reported "no".

In addition to this change, the fields of mutable structs that do not seem to be intended to be mutated have been made const. This should also result in some performance improvements.

Fixes #275
Closes #282

The `LinearOperator` constructor was insanely slow (~5μs) because of the
call to `get_nargs`. This removes that call and much of the 5-arg logic,
replacing with the use of `hasmethod`. Because all the types passed to
`hasmethod` are concrete, Julia's compiler can decide this at compile
time, so there is no actual runtime cost to this check.

This also removes the internal storage for `has_args5` since we can't
compute that at construction time without making the constructor very
slow. One consequence is a limited test breakage: `has_args5` now refers
to the "surface-level" operator only, and formerly some composite
operations (e.g., `hcat`, `+`, etc) would "lie" about whether there was
a 5-arg `mul!` available for the composite object in favor of reporting
whether that was true for all the constituents. With this change,
`has_args5` will report whether the composite object itself has a 5-arg
`mul!` available, which in cases here is "yes." But a few tests were
checking that the composite object reported "no".

In addition to this change, the fields of `mutable struct`s that do not
seem to be intended to be mutated have been made `const`. This should
also result in some performance improvements.

Fixes JuliaSmoothOptimizers#275
Closes JuliaSmoothOptimizers#282
Copilot AI review requested due to automatic review settings February 22, 2026 12:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets performance regressions in LinearOperator construction by removing the expensive get_nargs-based constructor logic and switching runtime behavior to prefer hasmethod checks for 5-arg mul! support, while also tightening operator structs by making non-mutated fields const.

Changes:

  • Reworked 5-arg mul! selection to use hasmethod checks and removed most of the prior “5-arg capability” bookkeeping from composite operator construction.
  • Simplified composite-operator constructors (+, *, hcat/vcat, BlockDiagonalOperator, etc.) to directly return LinearOperator{T,S}(...) without propagating args5.
  • Made many fields in mutable structs const to improve performance and compiler reasoning.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/abstract.jl Removes stored args5/allocation flags from LinearOperator, adds lazy buffers Mv/Mtu, and updates has_args5/isallocated5 plumbing.
src/operations.jl Switches 5-arg mul! dispatch to hasmethod, updates buffer allocation path for 2-arg prod!.
src/adjtrans.jl Updates adjoint/transpose mul! wrappers to use hasmethod and new buffers.
src/cat.jl Updates hcat/vcat to construct LinearOperator{T,S} directly (drops args5 propagation).
src/special-operators.jl Updates BlockDiagonalOperator to return LinearOperator{T,S} directly.
src/shifted_operators.jl Makes several fields const and removes use_prod5! method.
src/lbfgs.jl Marks many struct fields const and removes use_prod5! method.
src/lsr1.jl Marks many struct fields const and removes use_prod5! method.
src/TimedOperators.jl Marks wrapped fields const (but still forwards removed API and assumes 4-arg closures).
src/DiagonalHessianApproximation.jl Marks many struct fields const and removes 5-arg bookkeeping fields.
test/test_linop.jl Adjusts expectations around has_args5 for composites by marking several tests as @test_broken.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/TimedOperators.jl
Comment on lines 27 to 33
function TimedLinearOperator(op::AbstractLinearOperator{T}) where {T}
timer = TimerOutput()
prod!(res, x, α, β) = @timeit timer "prod" op.prod!(res, x, α, β)
tprod!(res, x, α, β) = @timeit timer "tprod" op.tprod!(res, x, α, β)
ctprod!(res, x, α, β) = @timeit timer "ctprod" op.ctprod!(res, x, α, β)
TimedLinearOperator{T}(timer, op, prod!, tprod!, ctprod!)
end
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TimedLinearOperator currently wraps op.prod!/tprod!/ctprod! with 4-arg closures unconditionally. If the wrapped operator was constructed with a 2-arg prod! (supported by LinearOperator(::Type, ..., prod!)), mul! will now take the hasmethod(..., (res,v,α,β)) fast-path and this wrapper will throw a MethodError when it calls op.prod!(res,x,α,β). Consider implementing timed wrappers that support both arities (e.g., define both 2-arg and 4-arg methods, or time mul!(res, op, x, α, β) directly) so timed operators behave the same as the underlying operator.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was in old code, but I agree that this was wrong. I fixed it.

Comment thread src/TimedOperators.jl
Comment thread src/operations.jl
Comment on lines 34 to 36
function mul!(res::AbstractMatrix, op::AbstractLinearOperator, m::AbstractMatrix{T}, α, β) where {T}
op.prod!(res, m, α, β)
end
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mul!(res::AbstractMatrix, op::AbstractLinearOperator, m::AbstractMatrix, α, β) always calls op.prod!(res, m, α, β). Operators constructed with a 2-arg prod! (documented in constructors.jl as supported) will throw a MethodError even for the common mul!(res, op, m) case where β == 0. Consider mirroring the vector path: use hasmethod to detect a 4-arg implementation, and otherwise fall back to the 2-arg prod! at least when β is zero (or implement a temp buffer strategy for β ≠ 0).

Copilot uses AI. Check for mistakes.
Comment thread src/abstract.jl
Comment thread src/abstract.jl Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 22, 2026

Codecov Report

❌ Patch coverage is 96.55172% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.86%. Comparing base (32dbc5e) to head (d77bd32).
⚠️ Report is 69 commits behind head on main.

Files with missing lines Patch % Lines
src/DiagonalHessianApproximation.jl 80.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #406      +/-   ##
==========================================
- Coverage   95.00%   94.86%   -0.14%     
==========================================
  Files          17       21       +4     
  Lines        1100     1168      +68     
==========================================
+ Hits         1045     1108      +63     
- Misses         55       60       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread test/test_linop.jl Outdated
Comment thread src/abstract.jl Outdated
@amontoison
Copy link
Copy Markdown
Member

amontoison commented Feb 22, 2026

@timholy We don't break any dependency with this PR. I am ok to merge it after a last pass on it from you.

@amontoison amontoison merged commit 4de94ea into JuliaSmoothOptimizers:main Feb 22, 2026
68 of 69 checks passed
@timholy timholy deleted the teh/args5 branch February 22, 2026 21:42
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.

Efficient get_nargs

3 participants