-
-
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
sparse vectors join the higher order function party #19690
Conversation
@nanosoldier |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
@test all(A[1,:] .* 3 .== AF[1,:] .* 3) | ||
#@test A[:,1] .* 3 == AF[:,1] .* 3 | ||
@test all(A[:,1] .* 3 .== AF[:,1] .* 3) | ||
#TODO: simple comparation with == returns false because the left side is a (two-dimensional) SparseMatrixCSC |
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 todo is really old, predates SparseVector being in base
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.
(Is this a request to clear this TODO in this pull request? Or a request to clear it later? Thanks!)
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 was about to do this in a local branch, but it would cause a conflict here
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.
Happy to sort that however you think best --- let me know. Thanks!
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.
it's just uncommenting, and deleting the all and todo lines
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.
Done. Thanks!
Benchmarks look good; the floatexp benchmarks seem noisy as usual. |
Why do we still have |
Do we still need the methods specialized on a pair of sparse matrices? |
It still doesn't handle mixtures of sparse vectors/matrices and scalars, correct? |
base/sparse/sparsevectors.jl contains a few not-exported [1] ... mostly defined against
Yes: The generic methods still appreciably lag the specialized methods in some cases. (A few performance improvements I have in mind might close the gap enough to nix some of the specialized methods. Post feature freeze work though.)
Correct: #19641 / merging #19667 blocks my solution for handling combinations including broadcast scalars. Best! |
@test broadcast(+, X, Y) == sparse(broadcast(+, fX, fY)) | ||
@test broadcast(*, X, Y) == sparse(broadcast(*, fX, fY)) | ||
@test broadcast(f, X, Y) == sparse(broadcast(f, fX, fY)) | ||
try Base.Broadcast.broadcast_indices(spzeros((shapeX .- 1)...), Y) |
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.
split this over multiple lines. if the intention is that the try should always throw, then add a @test false
after so it doesn't silently pass if it happens not to throw
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.
The intention is for the @test_throws
in the catch body to only evaluate when the statement in the try
body throws. Is there a better way to express this? Thanks!
(In other words, the test logic is: Check whether the arguments are shape-broadcast-incompatible. If so, make certain the method being @test_throws
'd throws.)
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.
Is there another way of determining shape compatibility than checking for something throwing? Calling some internal boolean function maybe? Otherwise seems like you might not be checking carefully enough which inputs do or do not trigger the test
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.
Is there another way of determining shape compatibility than checking for something throwing? Calling some internal boolean function maybe?
To the best of my knowledge none such exists. (I should instead call On second thought no, check_broadcast_indices
for clarity, but that similarly throws or returns nothing
.broadcast_indices
is correct as check_broadcast_indices
requires the broadcast shape (which you retrieve from broadcast_indices
) as a argument.) Thoughts? Thanks!
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.
(Split over multiple lines. Thanks!)
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.
hm maybe we should make that check function return a boolean instead of nothing-or-throw?
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.
Seems @timholy
designed that mechanism --- perhaps ping him? (Orthogonal to this pull request in any case?) Thanks!
@@ -1674,168 +1596,9 @@ end | |||
# 19304 | |||
@inferred hcat(sparse(rand(2,1)), eye(2,2)) | |||
|
|||
# Test that broadcast[!](f, [C::SparseMatrixCSC], A::SparseMatrixCSC, B::SparseMatrixCSC) | |||
# returns the correct (densely populated) result when f(zero(eltype(A)), zero(eltype(B))) != 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.
which set of the new tests replaces this?
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.
The broadcast[!] implementation specialized for pairs of (input) sparse vectors/matrices
testset replaces those tests.
X = sparse(broadcast(f, Array(A), Array(B), Array(C))) | ||
broadcast!(f, copy(X), A, B, C) # warmup for @allocated | ||
@test_broken (@allocated broadcast!(f, X, A, B, C)) == 0 | ||
# this last test allocates 16 bytes in the entry point for broadcast!, but none of the |
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.
figure out what was happening here? nevermind
Absent objections or requests for time, I plan to merge this Wednesday morning PDT. Best! |
You can now remove the workaround in this test, yes, and just do |
I have not finished reviewing this yet, but would like to do so today or tomorrow. edit: got through it all, locally undoing some of the rearrangements as https://github.com/JuliaLang/julia/compare/d8a57182f49787055b3a864cc86401988be4bcdf...tkelman:tk/sparsevechof?w=1 just for the purposes of reviewing made it a little easier to compare |
@test broadcast(*, X, Y, Z) == sparse(broadcast(*, fX, fY, fZ)) | ||
@test broadcast(f, X, Y, Z) == sparse(broadcast(f, fX, fY, fZ)) | ||
try Base.Broadcast.broadcast_indices(spzeros((shapeX .- 1)...), Y, Z) | ||
catch @test_throws DimensionMismatch broadcast(+, spzeros((shapeX .- 1)...), Y, Z) end |
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 try-catch (and the one a few lines lower) would also read more clearly if on multiple lines
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.
though I still think it would be preferable to have this serve as an independent verification of how broadcast_indices
is expected to behave for the differently-sized inputs, rather than relying on it throwing - if it were to be refactored to no longer throw, then this test would silently stop doing anything
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 try-catch (and the one a few lines lower) would also read more clearly if on multiple lines
Thanks for catching these other two instances! Split into multiple lines on push.
though I still think it would be preferable to have this serve as an independent verification of how broadcast_indices is expected to behave for the differently-sized inputs, rather than relying on it throwing - if it were to be refactored to no longer throw, then this test would silently stop doing anything
Agreed. Not being certain how best to achieve that strengthening at this time, can we revisit that point later? Thanks!
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.
leave a todo comment
# entry point for broadcast! with --track-allocation=all, but that first line | ||
# almost certainly should not allocate. so not certain what's going on. | ||
@test broadcast!(f, Q, X, Y, Z) == sparse(broadcast!(f, fQ, fX, fY, fZ)) | ||
# --> test shape checks for both braodcast and broadcast! entry points |
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.
"braodcast" typo
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.
Fixed on push. Thanks!
# separate horizontal and vertical expansion | ||
@test broadcast(op, A, B, C) == sparse(broadcast(op, fA, fB, fC)) | ||
@test broadcast!(op, X, A, B, C) == sparse(broadcast!(op, fX, fA, fB, fC)) | ||
# simultaneous horizontal and vertical expansion |
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.
does this require 4 inputs or is it covered by the new loop with only 3?
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.
Three input arguments suffice to test the relevant code paths, and indeed the testset titled "broadcast[!] implementation capable of handling >2 (input) sparse vectors/matrices"
covers the relevant code. Thanks!
# (5) Define _map_[not]zeropres! specialized for a pair of (input) sparse vectors/matrices. | ||
# (6) Define general _map_[not]zeropres! capable of handling >2 (input) sparse vectors/matrices. | ||
# (7) Define _broadcast_[not]zeropres! specialized for a pair of (input) sparse vectors/matrices. | ||
# (8) Define general _broadcast_[not]zeropres! capabel of handling >2 (input) sparse vectors/matrices. |
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.
"capabel" typo
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.
Good catch! Fixed on push. Thanks!
end | ||
# helper functions for these methods and some of those below | ||
@inline _densecoloffsets(A::SparseVector) = 0 | ||
@inline _densecoloffsets(A::SparseMatrixCSC) = 0:A.m:(A.m*A.n - 1) |
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.
why isn't the end point (A.m * (A.n-1))
here?
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.
Facepalm-worthy unintentional code obfuscation is why. Your suggestion is much better. Fixed on push. Thanks!
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.
A.m * (0:A.n-1)
might be even better, not sure
@inbounds for j in 1:C.n | ||
C.colptr[j] = Ck | ||
ks = _startindforbccol_all(j, expandshorzs, As) | ||
stopks = _stopindforbccol_all(j + 1, expandshorzs, As) |
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.
was the j+1
here a mistake or did you change the definition of this helper function?
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.
Changed the helper function's definition IIRC, but my memory is vague on that point. Thanks!
…e vector/matrix combinations. Extend generic sparse map[!]/broadcast[!] to sparse vectors and sparse vector/matrix combinations. Do so by introducing a common interface to SparseVector and SparseMatrixCSC for the purposes of map[!]/broadcast[!], and rewriting sparse map[!]/broadcast[!] against that interface. Relocate that code to a separate file/module base/sparse/higherorderfns.jl/SparseArrays.HigherOrderFns, loaded after definition of both SparseVector and SparseMatrixCSC.
Condense and systematize existing tests for generic sparse map[!]/broadcast[!], and extend to sparse vectors and vector/matrix combinations. Relocate new test code to a separate file test/sparse/higherorderfns.jl corresponding to base/sparse/higherorderfns.jl. (Test/sparse/sparsevector.jl is hypothetically confined to SparseVectors, and test/sparse/sparse.jl mostly dedicated to SparseMatrixCSCs.) Move older tests of sparse broadcast[!] into that new file as well.
Absent objections I plan to merge this once CI approves. Best! |
(AV i686 failure unrelated.) |
This pull request extends sparse
map[!]
/broadcast[!]
to sparse vectors and sparse vector/matrix combinations.Specifically, this pull request's first commit: (1) introduces a common interface to
SparseVector
andSparseMatrixCSC
sufficient for the purposes ofmap[!]
/broadcast[!]
; (2) rewrites the existing sparsemap[!]
/broadcast[!]
code against that interface; and (3) places the relevant code in the newSparseArrays.HigherOrderFns
submodule which lives inbase/sparse/higherorderfns.jl
, loaded after definition of bothSparseVector
andSparseMatrixCSC
.This pull request's second commit: (1) condenses and systematizes existing tests for generic sparse
map[!]
/broadcast[!]
; (2) extends those tests to sparse vectors and sparse vector/matrix combinations; and (3) collects those tests, as well older tests of sparsebroadcast[!]
, into the new test filetest/sparse/higherorderfns.jl
corresponding tobase/sparse/higherorderfns.jl
.Performance is more or less the same (previous generic sparse
map[!]
/broadcast[!]
methods versus those in this pull request), faster in some cases where I fixed a type stability regression.Best!
(Tangentially, this code (and likely an appreciable fraction of
sparsevector.jl
/sparsematrix.jl
) could be further unified and simplified by revisingSparseVector
/SparseMatrixCSC
with a common interface (cleaner than that in this pull request). If that idea sparks interest in this thread, I'll write a mini-julep at some point.)