Skip to content
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

stdlib/SparseArrays: add rmul! and lmul! of sparse matrix with Diagonal #29296

Merged
merged 5 commits into from
Sep 29, 2018
Merged

stdlib/SparseArrays: add rmul! and lmul! of sparse matrix with Diagonal #29296

merged 5 commits into from
Sep 29, 2018

Conversation

dkarrasch
Copy link
Member

Fixes #29111. Previously, lmul! and rmul! had no special method for multiplying a sparse matrix with a diagonal matrix in-place, but fell back to the generic LinearAlgebra/Diagonal-method. Tests had been included before.

Copy link
Sponsor Member

@StefanKarpinski StefanKarpinski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the nice contribution! I'm not the best person to review this but it looks good to me.

(n == size(D, 1)) || throw(DimensionMismatch())
Anzval = A.nzval
for col = 1:n, p = A.colptr[col]:(A.colptr[col+1]-1)
@inbounds Anzval[p] *= D.diag[col]
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be possible to gain a bit of speed here by manually hoisting the D.diag[col] lookup outside of the inner loop. Of course, the compiler may well be able to do that automatically. Depends on how much it knows about whether D.diag can change or move.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my experience with the SparseArray code, this is only needed when the object one getfield into is mutable. There is a lot of hoisting out fields for SparseMatrixCSC that was put there before #16371 (see discussion in #15668) but it shouldn't be needed now. Always worth benchmarking though...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry I can't follow. I did the change @StefanKarpinski suggested. Was that just (potentially) unnecessary or even "wrong"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I benchmarked the previous ("my") against the latest version ("Stefan's") on these:

P = sprand(12000, 12000, 0.01)
q = Diagonal(dropdims(sum(P, dims=2), dims=2))

and Stefan's won with 607.996 μs vs. 631.496 μs. So I guess it was worth it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, might be neglible. For

P = sprand(120000, 120000, 0.01)

(an order of magnitude larger) and q as before I get 104.832 ms vs. 103.934 ms.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is if the compiler tries to reload D.diag` every time in the loop or if it realizes that this will never change and hoist the load outside the loop.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it doesn't reload, at least it doesn't show up in performance loss. Should I, for better readability, then merge the two loops again as before, but leave the @inbounds upfront like in the last commit?

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't matter that much but perhaps it makes sense to at least have the two methods in this PR be consistent with eachother.

@dkarrasch dkarrasch changed the title Add rmul! and lmul! of sparse matrix with Diagonal stdlib/SparseArrays: add rmul! and lmul! of sparse matrix with Diagonal Sep 23, 2018
@dkarrasch
Copy link
Member Author

Is there anything that I can do more to get this merged? This is my first PR to Julia, so that would make me really proud. Maybe @andreasnoack may want to have a look at it? I can't seem to make much sense of the CI failures...

function lmul!(b::Number, A::SparseMatrixCSC)
lmul!(b, A.nzval)
return A
end

function rmul!(A::SparseMatrixCSC, D::Diagonal{T}) where T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T is never used in the function body so I think you can make this signature

function rmul!(A::SparseMatrixCSC, D::Diagonal). (same for lmul)

@mcognetta
Copy link
Contributor

Do you mind giving a small case where rmul and lmul would be called? Just doing regular multiplication doesn't hit those methods.

Also the CI failures are likely not because of your codet and just need to be restarted.

@dkarrasch
Copy link
Member Author

One use case is shown in #29111 : given a sparse "weight matrix", you may wish to turn it into a row-stochastic matrix (graph Laplacian) by row-normalizing by the sum of each row:

P = sprand(12000, 12000, 0.01)
q = Diagonal(dropdims(sum(P, dims=2), dims=2))
rmul!(P, q)

Very often, the weight matrix is huge and therefore sparse, and row-normalization corresponds to the rmul operation.

@fredrikekre fredrikekre added linear algebra Linear algebra sparse Sparse arrays labels Sep 28, 2018
Copy link
Member

@Sacha0 Sacha0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes look great! Thanks and welcome @dkarrasch! :)

Perhaps add tests exercising these new methods?

@dkarrasch
Copy link
Member Author

Thank you! I thought tests already existed at

@test dA * Diagonal(b) == rmul!(copy(sA), Diagonal(b))

but after a more thorough look I see that the result of rmul!´ and lmul` (a few lines below) were tested against a dense array. I'll change the above line then to

@test sA * Diagonal(b) == rmul!(copy(sA), Diagonal(b))

and similarly for lmul!, as I don't see any reason why one would want to have the result be dense in any case.

@Sacha0
Copy link
Member

Sacha0 commented Sep 28, 2018

Thanks for pointing the existing tests out! The existing tests look good to me. I imagine the rationale for testing against the result of an equivalent dense operation is that the relevant code paths are more likely to be disjoint, whereas e.g. sA * Diagonal(b) could quite well be implemented in terms of the mutating calls in question. Best!

@dkarrasch
Copy link
Member Author

I was not aware of these details (or better, never thought about it), but the density/sparsity doesn't matter in the test. So even though dA * Diagonal(b) returns a dense array and rmul!(copy(sA), Diagonal(b)) a sparse one (in this PR), this is all fine as long as they give the same matrix eventually. So I think the existing tests cover both rmul! and lmul! already.

Copy link
Member

@Sacha0 Sacha0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dkarrasch! :)

(Absent objections, requests for time, or someone beating me to it, I plan to merge these changes tomorrow morning PT or later.)

@Sacha0 Sacha0 merged commit 9454c8d into JuliaLang:master Sep 29, 2018
@Sacha0
Copy link
Member

Sacha0 commented Sep 29, 2018

Once again thanks and welcome @dkarrasch! :)

@chriscoey
Copy link

Thank you thank you! This improves the speed of my code

@dkarrasch
Copy link
Member Author

I know, mine, too! :-) But all I did was tracing back what scale! used to do in Julia v0.6, when it had a special method for sparse and diagonal. The change was really minor, after all. So, when and how is this "delivered" to the people officially? With Julia v1.0.2, since it's in a stdlib?

@Sacha0
Copy link
Member

Sacha0 commented Sep 30, 2018

So, when and how is this "delivered" to the people officially? With Julia v1.0.2, since it's in a stdlib?

Good question! I imagine these changes are a backport candidate. @ararslan?

@dkarrasch
Copy link
Member Author

@ararslan, is there any chance for a backport?

@ararslan
Copy link
Member

Yep should be possible to put into 1.0.3. cc @KristofferC

KristofferC pushed a commit that referenced this pull request Nov 28, 2018
…al (#29296)

* Add rmul! and lmul! of sparse matrix with Diagonal

(cherry picked from commit 9454c8d)
@KristofferC KristofferC mentioned this pull request Nov 28, 2018
61 tasks
KristofferC pushed a commit that referenced this pull request Dec 12, 2018
…al (#29296)

* Add rmul! and lmul! of sparse matrix with Diagonal

(cherry picked from commit 9454c8d)
@KristofferC KristofferC added performance Must go faster and removed backport pending 1.0 labels Dec 12, 2018
KristofferC pushed a commit that referenced this pull request Feb 11, 2019
…al (#29296)

* Add rmul! and lmul! of sparse matrix with Diagonal

(cherry picked from commit 9454c8d)
KristofferC pushed a commit that referenced this pull request Feb 20, 2020
…al (#29296)

* Add rmul! and lmul! of sparse matrix with Diagonal

(cherry picked from commit 9454c8d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
linear algebra Linear algebra performance Must go faster sparse Sparse arrays
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants