-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Faster blockdiag for uniform input value-index types #36013
Conversation
added another method to deal with the empty input case, blockdiag(X::AbstractSparseMatrixCSC{Tv, Ti}...) where {Tv, Ti <: Integer} instead of blockdiag(X::AbstractSparseMatrixCSC...) resulting in a The pull request, I would suggest is important given the case where all inputs have the same types for the value and index would be more common (and would benefit from increased performance) than the heterogenous case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice contribution, @irhum, and welcome! I have one comment that I'm curious what other people think about.
Co-authored-by: Daniel Karrasch <[email protected]>
You'll need to rename the function at the call sites as well. |
The function call sites have been changed to make the appropriate Since we also need to account for the case where no inputs are given, a clean solution seems to be to pass it as an optional argument to one of the methods, specifically: blockdiag(X::AbstractSparseMatrixCSC{Tv, Ti}...=spzeros(0,0)) where {Tv, Ti <: Integer} Since we're explicitly handing it, Ti = isempty(X) ? Int : promote_type(map(x->eltype(rowvals(x)), X)...) is always false, and hence can be replaced with Ti = promote_type(map(x->eltype(rowvals(x)), X)...) Opinions? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good in principle, but there is this subtle issue with empty arguments.
@@ -3318,16 +3318,23 @@ julia> blockdiag(sparse(2I, 3, 3), sparse(4I, 2, 2)) | |||
⋅ ⋅ ⋅ ⋅ 4 | |||
``` | |||
""" | |||
function blockdiag(X::AbstractSparseMatrixCSC{Tv, Ti}...=spzeros(0,0)) where {Tv, Ti <: Integer} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This default value makes the function change behavior for empty arguments. Currently, we have
julia> A = blockdiag()
0×0 SparseMatrixCSC{Union{},Int64} with 0 stored entries
julia> B = spzeros(0,0)
0×0 SparseMatrixCSC{Float64,Int64} with 0 stored entries
Changing that behavior may be for the better, but I'm not sure. This definitely requires a broader discussion, I'd say.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather not change the behavior inside this pull request anyway; the goal here is to provide increased speed when dealing with CSC arrays with the same types for the index and values; if needed, a separate issue can be opened whether the default should be changed.
Created a new commit that fixes this (the output is now SparseMatrixCSC{Union{},Int64}
) by explicitly spelling out the blockdiag() case, to make it clear what it does today, to avoid any confusions in the future.
@@ -3318,16 +3318,25 @@ julia> blockdiag(sparse(2I, 3, 3), sparse(4I, 2, 2)) | |||
⋅ ⋅ ⋅ ⋅ 4 | |||
``` | |||
""" | |||
blockdiag() = spzeros(Union{}, Int, 0, 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest to have
blockdiag() = spzeros(Union{}, Int, 0, 0) | |
blockdiag() = spzeros(promote_type(), Int, 0, 0) |
to avoid hardcoding some "non-sense" type and let the ecosystem decide. Pinging @tkf for this nasty "reduction over empty tuples" issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out, it is more sensible this way. I believe that resolves the issue for now, of speeding up this function while still making sure blockdiag() output remains unchanged. Would appreciate what @tkf has to say about the reduction on empty tuple issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Union{}
makes sense (i.e., it's the identity element of promote_type
as a monoid). Though promote_type()
sounds also fine. It's just a LISPy way of saying that we want the identity of promote_type
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @tkf. I just had no idea what might be subject to potential future design changes and what is "set in stone". I'll go ahead and merge this as is then.
Co-authored-by: Daniel Karrasch <[email protected]>
The current blockdiag implementation in SparseArrays uses type promotion to identify what types the values and index should be for the output SparseArray.
This can be quite slow when the input to blockdiag is many small, sparse arrays (for the sake of example, over 2500+). If all the input sparse arrays have the same value and index types, then it's not necessary to compute them via type promotion and we can directly proceed to creating the new matrix.
This can provide significant speedups; for M generated with
and benchmarking the existing implementation with BenchmarkTools
we get
running the same benchmark, with same M, with the new implementation, we get a speedup of nearly 1000x
the current blockdiag implementation is hence split into 3 possible methods