From 02e0254e35fa327d9f3f6969c85e9a3ac6699d57 Mon Sep 17 00:00:00 2001 From: Timo Kluck Date: Sat, 28 Dec 2019 19:35:51 +0100 Subject: [PATCH] SparseArrays: specialize zero(...) so result has nnz(...) = 0 Before this commit, the result of zero(x::AbstractSparseArray) is filled with stored zeros exactly where `x` has a stored value. That is a wasteful artifact of the default fallback implementation for `AbstractArray`. After this commit, we return a sparse array of the same dimensions as `x` without any stored values. This change is backwards incompatible where it relates to object identity. Before this commit, for mutable objects, every stored zero has the same identity. Example: julia> using SparseArrays julia> y = zero(BigInt[1,2,3]); y[1] === y[2] true julia> y = zero(sparse(BigInt[1,2,3])); y[1] === y[2] true julia> Base.zero(a::AbstractSparseArray) = spzeros(eltype(a), size(a)...) julia> y = zero(sparse(BigInt[1,2,3])); y[1] === y[2] false But this behaviour should be classified as a performance bug and can therefore be fixed in a minor (but not a patch) release. --- NEWS.md | 4 ++++ stdlib/SparseArrays/src/SparseArrays.jl | 4 +++- stdlib/SparseArrays/test/sparse.jl | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 0d440d9c8e893..ee67e60b7e8f5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -80,6 +80,10 @@ Standard library changes #### SparseArrays +* The return value of `zero(x::AbstractSparseArray)` has no stored zeros anymore ([#31835]). + Previously, it would have stored zeros wherever `x` had them. This makes the operation + constant time instead of `O()`. + #### Dates #### Statistics diff --git a/stdlib/SparseArrays/src/SparseArrays.jl b/stdlib/SparseArrays/src/SparseArrays.jl index 354a4bd098cb0..6d30f3ab761e4 100644 --- a/stdlib/SparseArrays/src/SparseArrays.jl +++ b/stdlib/SparseArrays/src/SparseArrays.jl @@ -10,7 +10,7 @@ using Base: ReshapedArray, promote_op, setindex_shape_check, to_shape, tail, using Base.Sort: Forward using LinearAlgebra -import Base: +, -, *, \, /, &, |, xor, == +import Base: +, -, *, \, /, &, |, xor, ==, zero import LinearAlgebra: mul!, ldiv!, rdiv!, cholesky, adjoint!, diag, eigen, dot, issymmetric, istril, istriu, lu, tr, transpose!, tril!, triu!, cond, diagm, factorize, ishermitian, norm, opnorm, lmul!, rmul!, tril, triu, matprod @@ -52,6 +52,8 @@ similar(D::Diagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = spzero similar(S::SymTridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = spzeros(T, dims...) similar(M::Tridiagonal, ::Type{T}, dims::Union{Dims{1},Dims{2}}) where {T} = spzeros(T, dims...) +zero(a::AbstractSparseArray) = spzeros(eltype(a), size(a)...) + const BiTriSym = Union{Bidiagonal,SymTridiagonal,Tridiagonal} function *(A::BiTriSym, B::BiTriSym) TS = promote_op(matprod, eltype(A), eltype(B)) diff --git a/stdlib/SparseArrays/test/sparse.jl b/stdlib/SparseArrays/test/sparse.jl index 071825e00b90f..6d72046a5ed2a 100644 --- a/stdlib/SparseArrays/test/sparse.jl +++ b/stdlib/SparseArrays/test/sparse.jl @@ -36,6 +36,7 @@ end @testset "issparse" begin @test issparse(sparse(fill(1,5,5))) @test !issparse(fill(1,5,5)) + @test nnz(zero(sparse(fill(1,5,5)))) == 0 end @testset "iszero specialization for SparseMatrixCSC" begin