Skip to content

Commit f47b08d

Browse files
committed
Use hasmethod for checking mul! implementation
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
1 parent 40edb65 commit f47b08d

11 files changed

Lines changed: 156 additions & 237 deletions

src/DiagonalHessianApproximation.jl

Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,23 @@ positive definite.
2020
"""
2121
mutable struct DiagonalPSB{T <: Real, I <: Integer, V <: AbstractVector{T}, F} <:
2222
AbstractDiagonalQuasiNewtonOperator{T}
23-
d::V # Diagonal of the operator
24-
nrow::I
25-
ncol::I
26-
symmetric::Bool
27-
hermitian::Bool
28-
prod!::F
29-
tprod!::F
30-
ctprod!::F
23+
const d::V # Diagonal of the operator
24+
const nrow::I
25+
const ncol::I
26+
const symmetric::Bool
27+
const hermitian::Bool
28+
const prod!::F
29+
const tprod!::F
30+
const ctprod!::F
3131
nprod::I
3232
ntprod::I
3333
nctprod::I
34-
args5::Bool
35-
use_prod5!::Bool # true for 5-args mul! and for composite operators created with operators that use the 3-args mul!
36-
allocated5::Bool # true for 5-args mul!, false for 3-args mul! until the vectors are allocated
3734
end
3835

3936
@doc (@doc DiagonalPSB) function DiagonalPSB(d::AbstractVector{T}) where {T <: Real}
4037
prod = (res, v, α, β) -> mulSquareOpDiagonal!(res, d, v, α, β)
4138
n = length(d)
42-
DiagonalPSB(d, n, n, true, true, prod, prod, prod, 0, 0, 0, true, true, true)
39+
DiagonalPSB(d, n, n, true, true, prod, prod, prod, 0, 0, 0)
4340
end
4441

4542
# update function
@@ -98,26 +95,23 @@ positive definite.
9895
"""
9996
mutable struct DiagonalAndrei{T <: Real, I <: Integer, V <: AbstractVector{T}, F} <:
10097
AbstractDiagonalQuasiNewtonOperator{T}
101-
d::V # Diagonal of the operator
102-
nrow::I
103-
ncol::I
104-
symmetric::Bool
105-
hermitian::Bool
106-
prod!::F
107-
tprod!::F
108-
ctprod!::F
98+
const d::V # Diagonal of the operator
99+
const nrow::I
100+
const ncol::I
101+
const symmetric::Bool
102+
const hermitian::Bool
103+
const prod!::F
104+
const tprod!::F
105+
const ctprod!::F
109106
nprod::I
110107
ntprod::I
111108
nctprod::I
112-
args5::Bool
113-
use_prod5!::Bool # true for 5-args mul! and for composite operators created with operators that use the 3-args mul!
114-
allocated5::Bool # true for 5-args mul!, false for 3-args mul! until the vectors are allocated
115109
end
116110

117111
@doc (@doc DiagonalAndrei) function DiagonalAndrei(d::AbstractVector{T}) where {T <: Real}
118112
prod = (res, v, α, β) -> mulSquareOpDiagonal!(res, d, v, α, β)
119113
n = length(d)
120-
DiagonalAndrei(d, n, n, true, true, prod, prod, prod, 0, 0, 0, true, true, true)
114+
DiagonalAndrei(d, n, n, true, true, prod, prod, prod, 0, 0, 0)
121115
end
122116

123117
# update function
@@ -155,20 +149,17 @@ https://doi.org/10.18637/jss.v060.i03
155149
"""
156150
mutable struct SpectralGradient{T <: Real, I <: Integer, F} <:
157151
AbstractDiagonalQuasiNewtonOperator{T}
158-
d::Vector{T} # Diagonal coefficient of the operator (multiple of the identity)
159-
nrow::I
160-
ncol::I
161-
symmetric::Bool
162-
hermitian::Bool
163-
prod!::F
164-
tprod!::F
165-
ctprod!::F
152+
const d::Vector{T} # Diagonal coefficient of the operator (multiple of the identity)
153+
const nrow::I
154+
const ncol::I
155+
const symmetric::Bool
156+
const hermitian::Bool
157+
const prod!::F
158+
const tprod!::F
159+
const ctprod!::F
166160
nprod::I
167161
ntprod::I
168162
nctprod::I
169-
args5::Bool
170-
use_prod5!::Bool # true for 5-args mul! and for composite operators created with operators that use the 3-args mul!
171-
allocated5::Bool # true for 5-args mul!, false for 3-args mul! until the vectors are allocated
172163
end
173164

174165
"""
@@ -186,7 +177,7 @@ function SpectralGradient(σ::T, n::I) where {T <: Real, I <: Integer}
186177
@assert σ > 0
187178
d = [σ]
188179
prod = (res, v, α, β) -> mulSquareOpDiagonal!(res, d, v, α, β)
189-
SpectralGradient(d, n, n, true, true, prod, prod, prod, 0, 0, 0, true, true, true)
180+
SpectralGradient(d, n, n, true, true, prod, prod, prod, 0, 0, 0)
190181
end
191182

192183
# update function
@@ -207,37 +198,34 @@ end
207198
"""
208199
DiagonalBFGS(d)
209200
210-
A diagonal approximation of the BFGS update inspired by
211-
Marnissi, Y., Chouzenoux, E., Benazza-Benyahia, A., & Pesquet, J. C. (2020).
201+
A diagonal approximation of the BFGS update inspired by
202+
Marnissi, Y., Chouzenoux, E., Benazza-Benyahia, A., & Pesquet, J. C. (2020).
212203
Majorize–minimize adapted Metropolis–Hastings algorithm.
213-
https://ieeexplore.ieee.org/abstract/document/9050537.
204+
https://ieeexplore.ieee.org/abstract/document/9050537.
214205
215206
# Arguments
216207
217208
- `d::AbstractVector`: initial diagonal approximation.
218209
"""
219210
mutable struct DiagonalBFGS{T <: Real, I <: Integer, V <: AbstractVector{T}, F} <:
220211
AbstractDiagonalQuasiNewtonOperator{T}
221-
d::V # Diagonal of the operator
222-
nrow::I
223-
ncol::I
224-
symmetric::Bool
225-
hermitian::Bool
226-
prod!::F
227-
tprod!::F
228-
ctprod!::F
212+
const d::V # Diagonal of the operator
213+
const nrow::I
214+
const ncol::I
215+
const symmetric::Bool
216+
const hermitian::Bool
217+
const prod!::F
218+
const tprod!::F
219+
const ctprod!::F
229220
nprod::I
230221
ntprod::I
231222
nctprod::I
232-
args5::Bool
233-
use_prod5!::Bool # true for 5-args mul! and for composite operators created with operators that use the 3-args mul!
234-
allocated5::Bool # true for 5-args mul!, false for 3-args mul! until the vectors are allocated
235223
end
236224

237225
@doc (@doc DiagonalBFGS) function DiagonalBFGS(d::AbstractVector{T}) where {T <: Real}
238226
prod = (res, v, α, β) -> mulSquareOpDiagonal!(res, d, v, α, β)
239227
n = length(d)
240-
DiagonalBFGS(d, n, n, true, true, prod, prod, prod, 0, 0, 0, true, true, true)
228+
DiagonalBFGS(d, n, n, true, true, prod, prod, prod, 0, 0, 0)
241229
end
242230

243231
# update function

src/TimedOperators.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ export TimedLinearOperator
55
mutable struct TimedLinearOperator{T, OP <: AbstractLinearOperator{T}, F, Ft, Fct} <:
66
AbstractLinearOperator{T}
77
timer::TimerOutput
8-
op::OP
9-
prod!::F
10-
tprod!::Ft
11-
ctprod!::Fct
8+
const op::OP
9+
const prod!::F
10+
const tprod!::Ft
11+
const ctprod!::Fct
1212
end
1313

1414
TimedLinearOperator{T}(

src/abstract.jl

Lines changed: 25 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ export AbstractLinearOperator,
77
ishermitian,
88
symmetric,
99
issymmetric,
10-
has_args5,
10+
has_args5, # TODO: deprecate
1111
isallocated5,
1212
nprod,
1313
ntprod,
1414
nctprod,
1515
reset!
1616

17-
mutable struct LinearOperatorException <: Exception
17+
struct LinearOperatorException <: Exception
1818
msg::AbstractString
1919
end
2020

@@ -44,21 +44,18 @@ other operators, with matrices and with scalars. Operators may
4444
be transposed and conjugate-transposed using the usual Julia syntax.
4545
"""
4646
mutable struct LinearOperator{T, S, I <: Integer, F, Ft, Fct} <: AbstractLinearOperator{T}
47-
nrow::I
48-
ncol::I
49-
symmetric::Bool
50-
hermitian::Bool
51-
prod!::F
52-
tprod!::Ft
53-
ctprod!::Fct
47+
const nrow::I
48+
const ncol::I
49+
const symmetric::Bool
50+
const hermitian::Bool
51+
const prod!::F
52+
const tprod!::Ft
53+
const ctprod!::Fct
5454
nprod::I
5555
ntprod::I
5656
nctprod::I
57-
args5::Bool
58-
use_prod5!::Bool # true for 5-args mul! and for composite operators created with operators that use the 3-args mul!
59-
Mv5::S
60-
Mtu5::S
61-
allocated5::Bool # true for 5-args mul!, false for 3-args mul! until the vectors are allocated
57+
Mv::Union{S, Nothing}
58+
Mtu::Union{S, Nothing}
6259
end
6360

6461
function LinearOperator{T, S}(
@@ -73,12 +70,6 @@ function LinearOperator{T, S}(
7370
ntprod::I,
7471
nctprod::I,
7572
) where {T, S, I <: Integer, F, Ft, Fct}
76-
Mv5, Mtu5 = S(undef, 0), S(undef, 0)
77-
nargs = get_nargs(prod!)
78-
args5 = (nargs == 4)
79-
(args5 == false) || (nargs != 2) || throw(LinearOperatorException("Invalid number of arguments"))
80-
allocated5 = args5 ? true : false
81-
use_prod5! = args5 ? true : false
8273
return LinearOperator{T, S, I, F, Ft, Fct}(
8374
nrow,
8475
ncol,
@@ -90,11 +81,8 @@ function LinearOperator{T, S}(
9081
nprod,
9182
ntprod,
9283
nctprod,
93-
args5,
94-
use_prod5!,
95-
Mv5,
96-
Mtu5,
97-
allocated5,
84+
nothing,
85+
nothing,
9886
)
9987
end
10088

@@ -147,56 +135,7 @@ LinearOperator{T}(
147135
S::Type = Vector{T},
148136
) where {T} = LinearOperator{T, S}(nrow, ncol, symmetric, hermitian, prod!, tprod!, ctprod!)
149137

150-
# create operator from other operators with +, *, vcat,...
151-
# TODO: this is not a type, so it should not be uppercase
152-
function CompositeLinearOperator(
153-
T::Type,
154-
nrow::I,
155-
ncol::I,
156-
symmetric::Bool,
157-
hermitian::Bool,
158-
prod!::F,
159-
tprod!::Ft,
160-
ctprod!::Fct,
161-
args5::Bool,
162-
::Type{S},
163-
) where {S, I <: Integer, F, Ft, Fct}
164-
Mv5, Mtu5 = S(undef, 0), S(undef, 0)
165-
allocated5 = true
166-
use_prod5! = true
167-
return LinearOperator{T, S, I, F, Ft, Fct}(
168-
nrow,
169-
ncol,
170-
symmetric,
171-
hermitian,
172-
prod!,
173-
tprod!,
174-
ctprod!,
175-
0,
176-
0,
177-
0,
178-
args5,
179-
use_prod5!,
180-
Mv5,
181-
Mtu5,
182-
allocated5,
183-
)
184-
end
185-
186-
# backward compatibility (not inferrable)
187-
CompositeLinearOperator(
188-
T::Type,
189-
nrow::I,
190-
ncol::I,
191-
symmetric::Bool,
192-
hermitian::Bool,
193-
prod!::F,
194-
tprod!::Ft,
195-
ctprod!::Fct,
196-
args5::Bool;
197-
S::Type = Vector{T},
198-
) where {I <: Integer, F, Ft, Fct} =
199-
CompositeLinearOperator(T, nrow, ncol, symmetric, hermitian, prod!, tprod!, ctprod!, args5, S)
138+
const CompositeLinearOperator = LinearOperator # backwards compatibility
200139

201140
nprod(op::AbstractLinearOperator) = op.nprod
202141
ntprod(op::AbstractLinearOperator) = op.ntprod
@@ -213,18 +152,24 @@ Determine whether the operator can work with the 5-args `mul!`.
213152
If `false`, storage vectors will be generated at the first call of
214153
the 5-args `mul!`.
215154
No additional vectors are generated when using the 3-args `mul!`.
155+
156+
!!! warning
157+
`has_nargs5` can be very slow. A better option is to use Julia's `hasmethod`
158+
at points in the code where the concrete types of objects used in `mul!` are known.
159+
160+
`has_nargs5` may be removed in a future release.
216161
"""
217-
has_args5(op::AbstractLinearOperator) = op.args5
218-
use_prod5!(op::AbstractLinearOperator) = op.use_prod5!
219-
isallocated5(op::AbstractLinearOperator) = op.allocated5
162+
has_args5(op::AbstractLinearOperator) = get_nargs(op.prod!) == 4
163+
164+
isallocated5(op::LinearOperator) = !(isnothing(op.Mv) || isnothing(op.Mtu))
220165

221166
has_args5(op::AbstractMatrix) = true # Needed for BlockDiagonalOperator
222167

223168
# Alert user of the need for storage_type method definition for arbitrary, user defined operators
224169
storage_type(op::AbstractLinearOperator) = error("please implement storage_type for $(typeof(op))")
225170

226-
storage_type(op::LinearOperator) = typeof(op.Mv5)
227-
storage_type(M::AbstractMatrix{T}) where {T} = Vector{T}
171+
storage_type(::LinearOperator{T, S}) where {T, S} = S
172+
storage_type(::AbstractMatrix{T}) where {T} = Vector{T}
228173

229174
# Lazy wrappers
230175
storage_type(op::Adjoint) = storage_type(parent(op))

0 commit comments

Comments
 (0)