@@ -25,7 +25,6 @@ import LinearAlgebra: (\), AdjointFactorization,
2525using SparseArrays
2626using SparseArrays: getcolptr, AbstractSparseVecOrMat
2727export
28- CholmodWorkspace,
2928 Dense,
3029 Factor,
3130 Sparse
@@ -259,8 +258,50 @@ function Sparse(p::Ptr{cholmod_sparse})
259258 Sparse {jlxtype(s.xtype, s.dtype)} (p)
260259end
261260
261+ """
262+ CholmodWorkspace()
263+
264+ Reusable workspace for allocation-free CHOLMOD solves.
265+
266+ `cholmod_solve2` allocates two temporary dense matrices (Y and E) on every solve.
267+ A `CholmodWorkspace` holds those buffers across calls so that CHOLMOD reuses them
268+ instead of reallocating. It also pre-allocates the `cholmod_dense_struct` wrappers
269+ for `x` and `b`, eliminating all Julia-side heap allocations per solve.
270+
271+ Each [`Factor`](@ref) embeds its own workspace and uses it automatically in
272+ `ldiv!(x, F, b)`. For multi-thread solves with a single factor, use `copy(F)`
273+ for every thread so each copy gets its own independent workspace and lock.
274+
275+ The workspace is finalizer-protected. The Y and E buffers are freed automatically
276+ when the workspace is garbage-collected.
277+ """
278+ mutable struct CholmodWorkspace
279+ dense_x:: cholmod_dense_struct
280+ dense_b:: cholmod_dense_struct
281+ X:: Ref{Ptr{cholmod_dense_struct}}
282+ Y:: Ref{Ptr{cholmod_dense_struct}}
283+ E:: Ref{Ptr{cholmod_dense_struct}}
284+
285+ function CholmodWorkspace ()
286+ ws = new (
287+ cholmod_dense_struct (), # all fields will be written in ldiv! before use
288+ cholmod_dense_struct (),
289+ Ref (Ptr {cholmod_dense_struct} (C_NULL )),
290+ Ref (Ptr {cholmod_dense_struct} (C_NULL )),
291+ Ref (Ptr {cholmod_dense_struct} (C_NULL ))
292+ )
293+ finalizer (ws) do w
294+ w. Y[] != C_NULL && free! (w. Y[])
295+ w. E[] != C_NULL && free! (w. E[])
296+ end
297+ ws
298+ end
299+ end
300+
262301mutable struct Factor{Tv<: VTypes , Ti<: ITypes } <: Factorization{Tv}
263302 ptr:: Ptr{cholmod_factor}
303+ workspace:: CholmodWorkspace
304+ lock:: ReentrantLock
264305 function Factor {Tv, Ti} (ptr:: Ptr{cholmod_factor} , register_finalizer = true ) where {Tv, Ti}
265306 if ptr == C_NULL
266307 throw (ArgumentError (" factorization construction failed for " *
@@ -277,7 +318,7 @@ mutable struct Factor{Tv<:VTypes, Ti<:ITypes} <: Factorization{Tv}
277318 free! (ptr, Ti)
278319 throw (CHOLMODException (" dtype=$(dtyp (Tv)) not supported" ))
279320 end
280- F = new (ptr)
321+ F = new (ptr, CholmodWorkspace (), ReentrantLock () )
281322 if register_finalizer
282323 finalizer (free!, F)
283324 end
@@ -312,6 +353,8 @@ Base.pointer(x::Dense{Tv}) where {Tv} = Base.unsafe_convert(Ptr{cholmod_dense},
312353Base. pointer (x:: Sparse{Tv} ) where {Tv} = Base. unsafe_convert (Ptr{cholmod_sparse}, x)
313354Base. pointer (x:: Factor{Tv} ) where {Tv} = Base. unsafe_convert (Ptr{cholmod_factor}, x)
314355
356+ getworkspace (F:: Factor ) = F. workspace
357+
315358function _factor_components (is_ll)
316359 is_ll ? (:L , :U , :PtL , :UP ) : (:L , :U , :PtL , :UP , :D , :LD , :DU , :PtLD , :DUP )
317360end
@@ -858,32 +901,6 @@ function Base.convert(::Type{Dense{Tnew}}, A::Dense{T}) where {Tnew, T}
858901end
859902Base. convert (:: Type{Dense{T}} , A:: Dense{T} ) where T = A
860903
861- # Just calling Dense(x) or Dense(b) will allocate new
862- # `cholmod_dense_struct`s in CHOLMOD. Instead, we want to reuse
863- # the existing memory. We can do this by creating new
864- # `cholmod_dense_struct`s and filling them manually.
865- function wrap_dense_and_ptr (x:: StridedVecOrMat{T} ) where {T <: VTypes }
866- dense_x = cholmod_dense_struct ()
867- dense_x. nrow = size (x, 1 )
868- dense_x. ncol = size (x, 2 )
869- dense_x. nzmax = length (x)
870- dense_x. d = stride (x, 2 )
871- dense_x. x = pointer (x)
872- dense_x. z = C_NULL
873- dense_x. xtype = xtyp (eltype (x))
874- dense_x. dtype = dtyp (eltype (x))
875- return dense_x, pointer_from_objref (dense_x)
876- end
877- # We need to use a special handling for the case of `Dense`
878- # input arrays since the `pointer` refers to the pointer to the
879- # `cholmod_dense`, not to the array values themselves as for
880- # standard arrays.
881- function wrap_dense_and_ptr (x:: Dense{T} ) where {T <: VTypes }
882- dense_x_ptr = x. ptr
883- dense_x = unsafe_load (dense_x_ptr)
884- return dense_x, pointer_from_objref (dense_x)
885- end
886-
887904# This constructor assumes zero based colptr and rowval
888905function Sparse (m:: Integer , n:: Integer ,
889906 colptr0:: Vector{Ti} , rowval0:: Vector{Ti} ,
@@ -1326,8 +1343,8 @@ end
13261343@inline function getproperty (F:: Factor , sym:: Symbol )
13271344 if sym === :p
13281345 return get_perm (F)
1329- elseif sym === :ptr
1330- return getfield (F, :ptr )
1346+ elseif sym === :ptr || sym === :workspace || sym === :lock # add workspace and lock
1347+ return getfield (F, sym )
13311348 else
13321349 return FactorComponent (F, sym)
13331350 end
@@ -1434,11 +1451,11 @@ end
14341451
14351452function cholesky! (F:: Factor{Tv} , A:: Sparse{Tv} ;
14361453 shift:: Real = 0.0 , check:: Bool = true ) where Tv
1437- # Compute the numerical factorization
1438- @cholmod_param final_ll = true begin
1439- factorize_p! (A, shift, F)
1454+ @lock F. lock begin
1455+ @cholmod_param final_ll = true begin
1456+ factorize_p! (A, shift, F)
1457+ end
14401458 end
1441-
14421459 check && (issuccess (F) || throw (LinearAlgebra. PosDefException (1 )))
14431460 return F
14441461end
@@ -1609,12 +1626,10 @@ LinearAlgebra._cholesky(A::Union{SparseMatrixCSC{T}, SparseMatrixCSC{Complex{T}}
16091626
16101627function ldlt! (F:: Factor{Tv} , A:: Sparse{Tv} ;
16111628 shift:: Real = 0.0 , check:: Bool = true ) where Tv
1612- # Makes it an LDLt
1613- change_factor! (F, false , false , true , false )
1614-
1615- # Compute the numerical factorization
1616- factorize_p! (A, shift, F)
1617-
1629+ @lock F. lock begin
1630+ change_factor! (F, false , false , true , false )
1631+ factorize_p! (A, shift, F)
1632+ end
16181633 check && (issuccess (F) || throw (LinearAlgebra. ZeroPivotException (1 )))
16191634 return F
16201635end
@@ -1916,118 +1931,53 @@ const AbstractSparseVecOrMatInclAdjAndTrans = Union{AbstractSparseVecOrMat, AdjO
19161931 throw (ArgumentError (" self-adjoint sparse system solve not implemented for sparse rhs B," *
19171932 " consider to convert B to a dense array" ))
19181933
1919- """
1920- CholmodWorkspace()
1921-
1922- Reusable workspace for allocation-free calls to `ldiv!(x, L, b, ws)`.
1923-
1924- `cholmod_solve2` allocates two temporary dense matrices (Y and E) on every solve.
1925- A `CholmodWorkspace` holds those buffers across calls so that CHOLMOD reuses them
1926- instead of reallocating. It also pre-allocates the `cholmod_dense_struct` wrappers
1927- for `x` and `b`.
1928- This eliminates all Julia-side heap allocations per solve.
1929-
1930- A finalizer frees Y adn E automatically when the worspace is garbage-collected.
1931-
1932- A single workspace can be reused across calls with different arrays or a different number of RHS columns since
1933- CHOLMOD will resize Y and E.
1934-
1935- # Example
1936- ```julia
1937- F = cholesky(A)
1938- ws = CholmodWorkspace()
1939- for b in rhs_list
1940- ldiv!(x, F, b, ws)
1941- end
1942- ```
1943- """
1944-
1945- mutable struct CholmodWorkspace
1946- dense_x:: cholmod_dense_struct
1947- dense_b:: cholmod_dense_struct
1948- X:: Ref{Ptr{cholmod_dense_struct}}
1949- Y:: Ref{Ptr{cholmod_dense_struct}}
1950- E:: Ref{Ptr{cholmod_dense_struct}}
1934+ # Julia array -> fill ws.dense_b fields and return pointer to it
1935+ # CHOLMOD Dense object -> struct is already set up by CHOLMOD, return b.ptr directly
1936+ @inline function _setup_bptr (b:: StridedVecOrMat{T} , ws:: CholmodWorkspace ) where T<: VTypes
1937+ ws. dense_b. nrow = size (b, 1 )
1938+ ws. dense_b. ncol = size (b, 2 )
1939+ ws. dense_b. nzmax = length (b)
1940+ ws. dense_b. d = stride (b, 2 )
1941+ ws. dense_b. x = pointer (b)
1942+ ws. dense_b. z = C_NULL
1943+ ws. dense_b. xtype = xtyp (T)
1944+ ws. dense_b. dtype = dtyp (T)
1945+ return Ptr {cholmod_dense_struct} (pointer_from_objref (ws. dense_b))
1946+ end
1947+ @inline _setup_bptr (b:: Dense{<:VTypes} , :: CholmodWorkspace ) = b. ptr
19511948
1952- function CholmodWorkspace ()
1953- ws = new (
1954- cholmod_dense_struct (), # all fields will be written in ldiv! before use
1955- cholmod_dense_struct (),
1956- Ref (Ptr {cholmod_dense_struct} (C_NULL )),
1957- Ref (Ptr {cholmod_dense_struct} (C_NULL )),
1958- Ref (Ptr {cholmod_dense_struct} (C_NULL ))
1959- )
1960- finalizer (ws) do w
1961- w. Y[] != C_NULL && free! (w. Y[])
1962- w. E[] != C_NULL && free! (w. E[])
1963- end
1964- ws
1965- end
1966- end
1967-
1968- # in-place ldiv! and optional allocation-free ldiv!
19691949for TI in IndexTypes
1970- @eval function ldiv! (x:: StridedVecOrMat{T} ,
1971- L:: Factor{T, $TI} ,
1972- b:: StridedVecOrMat{T} ) where {T<: VTypes }
1973- if x === b
1974- throw (ArgumentError (" output array must not be aliased with input array" ))
1975- end
1976- if size (L, 1 ) != size (b, 1 )
1977- throw (DimensionMismatch (" Factorization and RHS should have the same number of rows. " *
1978- " Factorization has $(size (L, 2 )) rows, but RHS has $(size (b, 1 )) rows." ))
1979- end
1980- if size (L, 2 ) != size (x, 1 )
1981- throw (DimensionMismatch (" Factorization and solution should match sizes. " *
1982- " Factorization has $(size (L, 1 )) columns, but solution has $(size (x, 1 )) rows." ))
1983- end
1984- if size (x, 2 ) != size (b, 2 )
1985- throw (DimensionMismatch (" Solution and RHS should have the same number of columns. " *
1986- " Solution has $(size (x, 2 )) columns, but RHS has $(size (b, 2 )) columns." ))
1987- end
1988- if ! issuccess (L)
1989- s = unsafe_load (pointer (L))
1990- if s. is_ll == 1
1991- throw (LinearAlgebra. PosDefException (s. minor))
1992- else
1993- throw (LinearAlgebra. ZeroPivotException (s. minor))
1950+ @eval function solve! (x:: StridedVecOrMat{T} ,
1951+ L:: Factor{T, $TI} ,
1952+ b:: StridedVecOrMat{T} ;
1953+ ws = getworkspace (L)) where {T<: VTypes }
1954+ @lock L. lock begin
1955+ ws. dense_x. nrow = size (x, 1 )
1956+ ws. dense_x. ncol = size (x, 2 )
1957+ ws. dense_x. nzmax = length (x)
1958+ ws. dense_x. d = stride (x, 2 )
1959+ ws. dense_x. x = pointer (x)
1960+ ws. dense_x. z = C_NULL
1961+ ws. dense_x. xtype = xtyp (T)
1962+ ws. dense_x. dtype = dtyp (T)
1963+
1964+ ws. X[] = Ptr {cholmod_dense_struct} (pointer_from_objref (ws. dense_x))
1965+ Bptr = _setup_bptr (b, ws)
1966+ status = GC. @preserve x b ws begin
1967+ $ (cholname (:solve2 , TI))(
1968+ CHOLMOD_A, L,
1969+ Bptr, C_NULL ,
1970+ ws. X, C_NULL ,
1971+ ws. Y, ws. E,
1972+ getcommon ($ TI))
19941973 end
1974+ @assert ! iszero (status)
19951975 end
1996-
1997- # Just calling Dense(x) or Dense(b) will allocate new
1998- # `cholmod_dense_struct`s in CHOLMOD. Instead, we want to reuse
1999- # the existing memory. We can do this by creating new
2000- # `cholmod_dense_struct`s and filling them manually.
2001- dense_x, dense_x_ptr = wrap_dense_and_ptr (x)
2002- dense_b, dense_b_ptr = wrap_dense_and_ptr (b)
2003-
2004- X_Handle = Ptr {cholmod_dense_struct} (dense_x_ptr)
2005- Y_Handle = Ptr {cholmod_dense_struct} (C_NULL )
2006- E_Handle = Ptr {cholmod_dense_struct} (C_NULL )
2007- status = GC. @preserve x dense_x b dense_b begin
2008- $ (cholname (:solve2 , TI))(
2009- CHOLMOD_A, L,
2010- Ref (dense_b), C_NULL ,
2011- Ref (X_Handle), C_NULL ,
2012- Ref (Y_Handle),
2013- Ref (E_Handle),
2014- getcommon ($ TI))
2015- end
2016- if Y_Handle != C_NULL
2017- free! (Y_Handle)
2018- end
2019- if E_Handle != C_NULL
2020- free! (E_Handle)
2021- end
2022- @assert ! iszero (status)
2023-
2024- return x
20251976 end
20261977
20271978 @eval function ldiv! (x:: StridedVecOrMat{T} ,
20281979 L:: Factor{T, $TI} ,
2029- b:: StridedVecOrMat{T} ,
2030- ws:: CholmodWorkspace ) where {T<: VTypes }
1980+ b:: StridedVecOrMat{T} ) where {T<: VTypes }
20311981 if x === b
20321982 throw (ArgumentError (" output array must not be aliased with input array" ))
20331983 end
@@ -2051,38 +2001,7 @@ for TI in IndexTypes
20512001 throw (LinearAlgebra. ZeroPivotException (s. minor))
20522002 end
20532003 end
2054-
2055- # Update all cholmod_dense_structs fields on every call in case dimensions or T change
2056- ws. dense_x. nrow = size (x, 1 )
2057- ws. dense_x. ncol = size (x, 2 )
2058- ws. dense_x. nzmax = length (x)
2059- ws. dense_x. d = stride (x, 2 )
2060- ws. dense_x. x = pointer (x)
2061- ws. dense_x. z = C_NULL
2062- ws. dense_x. xtype = xtyp (T)
2063- ws. dense_x. dtype = dtyp (T)
2064-
2065- ws. dense_b. nrow = size (b, 1 )
2066- ws. dense_b. ncol = size (b, 2 )
2067- ws. dense_b. nzmax = length (b)
2068- ws. dense_b. d = stride (b, 2 )
2069- ws. dense_b. x = pointer (b)
2070- ws. dense_b. z = C_NULL
2071- ws. dense_b. xtype = xtyp (T)
2072- ws. dense_b. dtype = dtyp (T)
2073-
2074- ws. X[] = Ptr {cholmod_dense_struct} (pointer_from_objref (ws. dense_x))
2075- Bptr = Ptr {cholmod_dense_struct} (pointer_from_objref (ws. dense_b))
2076- status = GC. @preserve x b ws begin
2077- $ (cholname (:solve2 , TI))(
2078- CHOLMOD_A, L,
2079- Bptr, C_NULL ,
2080- ws. X, C_NULL ,
2081- ws. Y, ws. E,
2082- getcommon ($ TI))
2083- end
2084- @assert ! iszero (status)
2085-
2004+ solve! (x, L, b)
20862005 return x
20872006 end
20882007end
0 commit comments