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

MIT-licensed SparseMatrixCSC fkeep! and children #14702

Merged
merged 1 commit into from
Feb 26, 2016

Conversation

Sacha0
Copy link
Member

@Sacha0 Sacha0 commented Jan 17, 2016

Followup to #13001 and #14631. This pull request replaces the LGPL-licensed SparseMatrixCSC fkeep! method and children tril!, triu!, droptol!, and dropzeros[!] (noticed signature during cleanup) in base/sparse/csparse.jl with MIT-licensed versions.

These methods run in O(A.n, nnz(A)) time and require no space beyond that passed in. The new methods perform better than the old methods, particularly for large matrices; are the new implementations missing anything that would require additional time?

When editing base/sparse/csparse.jl to remove the existing methods, I folded all blocks to avoid peaking at the LGPL code. So someone should check that the removal was graceful given it was blind but for method signatures.

Benchmarks:

using Benchmarks: @benchmark, SummaryStatistics

function prettytimes(bench)
    stats = SummaryStatistics(bench)
    timecenter = stats.elapsed_time_center
    timelower = get(stats.elapsed_time_lower)
    timeupper = get(stats.elapsed_time_upper)
    # based on Benchmarks.pretty_time_string
    timecenter < 1_000.0 ? (scalefactor = 1.0; units = "ns") :
        timecenter < 1_000_000.0 ? (scalefactor = 1_000.0; units = "μs") :
            timecenter < 1_000_000_000.0 ? (scalefactor = 1_000_000.0; units = "ms") :
                (scalefactor = 1_000_000_000.0; units = " s")
    @sprintf("%6.2f %s [%6.2f,%6.2f]", timecenter/scalefactor, units, timelower/scalefactor, timeupper/scalefactor)
end

smallN, smallerN = 600, 400;
smallsqrA = sprand(smallN, smallN, 0.01);
smallrectA = sprand(smallN, smallerN, 0.01);
largeN, largerN = 100000, 200000;
largesqrA = sprand(largeN, largeN, 0.001);
largerectA = sprand(largeN, largerN, 0.001);

wm, wt = 12, 25;
println(" $(lpad("method ", wm)) $(lpad("small square A", wt)), $(lpad("small rect A", wt)) | $(lpad("large square A", wt)), $(lpad("large rect A", wt)) ")
@printf("%s: %s , %s | %s, %s\n", lpad("tril!", wm),
    prettytimes(@benchmark triu!(copy(smallsqrA))),
    prettytimes(@benchmark triu!(copy(smallrectA))),
    prettytimes(@benchmark triu!(copy(largesqrA))),
    prettytimes(@benchmark triu!(copy(largerectA))) )
@printf("%s: %s , %s | %s, %s\n", lpad("triu!", wm),
    prettytimes(@benchmark triu!(copy(smallsqrA))),
    prettytimes(@benchmark triu!(copy(smallrectA))),
    prettytimes(@benchmark triu!(copy(largesqrA))),
    prettytimes(@benchmark triu!(copy(largerectA))) )
@printf("%s: %s , %s | %s, %s\n", lpad("droptol!", wm),
    prettytimes(@benchmark Base.SparseArrays.droptol!(copy(smallsqrA), 0.5)),
    prettytimes(@benchmark Base.SparseArrays.droptol!(copy(smallrectA), 0.5)),
    prettytimes(@benchmark Base.SparseArrays.droptol!(copy(largesqrA), 0.5)),
    prettytimes(@benchmark Base.SparseArrays.droptol!(copy(largerectA), 0.5)) )
@printf("%s: %s , %s | %s, %s\n", lpad("dropzeros!", wm),
    prettytimes(@benchmark Base.SparseArrays.dropzeros!(copy(smallsqrA))),
    prettytimes(@benchmark Base.SparseArrays.dropzeros!(copy(smallrectA))),
    prettytimes(@benchmark Base.SparseArrays.dropzeros!(copy(largesqrA))),
    prettytimes(@benchmark Base.SparseArrays.dropzeros!(copy(largerectA))) )

On master:

    method             small square A,              small rect A |            large square A,              large rect A
     tril!:   5.69 μs [  5.62,  5.75] ,   2.50 μs [  2.48,  2.52] |  16.45 ms [ 15.39, 17.51],  47.47 ms [ 45.94, 49.01]
     triu!:   5.66 μs [  5.61,  5.71] ,   2.46 μs [  2.45,  2.47] |  16.32 ms [ 15.33, 17.31],  47.88 ms [ 45.88, 49.89]
  droptol!:   4.64 μs [  4.61,  4.67] ,   3.09 μs [  3.05,  3.13] |  14.32 ms [  9.82, 18.83],  29.77 ms [ 16.43, 43.10]
dropzeros!:  13.10 μs [ 13.07, 13.14] ,   8.57 μs [  8.53,  8.62] |  25.05 ms [ 24.10, 25.99],  50.34 ms [ 49.09, 51.59]

On this PR's branch:

    method             small square A,              small rect A |            large square A,              large rect A
     tril!:   4.31 μs [  4.30,  4.32] ,   1.60 μs [  1.60,  1.61] |   6.51 ms [  5.26,  7.76],  18.19 ms [ 15.06, 21.32]
     triu!:   4.31 μs [  4.29,  4.33] ,   1.60 μs [  1.59,  1.60] |   6.51 ms [  5.27,  7.74],  17.90 ms [ 15.17, 20.64]
  droptol!:   4.47 μs [  4.45,  4.50] ,   2.17 μs [  2.16,  2.19] |   8.59 ms [  3.83, 13.34],  18.41 ms [  4.40, 32.42]
dropzeros!:  10.50 μs [ 10.42, 10.59] ,   6.48 μs [  6.46,  6.51] |  11.45 ms [ 10.88, 12.01],  22.98 ms [ 22.15, 23.81]

The dropzeros! benchmark isn't particularly meaningful given the absence of stored zeros in the test matrices. Thanks, and best!

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 17, 2016

Travis discontent unrelated?

@tkelman
Copy link
Contributor

tkelman commented Jan 17, 2016

yeah

fatal error during bootstrap:
(parse-error "read: unexpected ']'")
ERROR: fatal error loading system image

there must be a race condition in a makefile or the parser or something...

@tkelman tkelman added the sparse Sparse arrays label Jan 17, 2016
@ViralBShah
Copy link
Member

Nice performance gain. Would you be able to add some tests as well? Doesn't seem like there are any at the moment.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 17, 2016

Would you be able to add some tests as well? Doesn't seem like there are any at the moment.

tril! and triu! tests exist in 876240c/test/sparsedir/sparse.jl#L974-L975. A droptol! test exists in 876240c/test/sparsedir/sparse.jl#L956-L957. The preceding tests implicitly test fkeep!. dropzeros! appears to lack tests.

I added a test for dropzeros!. Are the existing tests for tril!, triu!, droptol!, and thereby fkeep! adequate, or would you like additional tests? If the latter, what would you like to see? Thanks for the review!

@hayd
Copy link
Member

hayd commented Jan 24, 2016

lgtm

@ViralBShah
Copy link
Member

@tkelman Merge this?

@jrevels jrevels added the potential benchmark Could make a good benchmark in BaseBenchmarks label Jan 24, 2016
triu!(A::SparseMatrixCSC, k::Integer = 0) = fkeep!(A, TriuFunc(), k)

immutable DroptolFunc <: Base.Func{4} end
call{Tv,Ti}(::DroptolFunc, i::Ti, j::Ti, x::Tv, tol::Real) = abs(x) >= tol
Copy link
Contributor

Choose a reason for hiding this comment

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

previous implementation of droptol used > here

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed, thanks!

@tkelman tkelman added the needs tests Unit tests are required for this change label Jan 25, 2016
@hayd
Copy link
Member

hayd commented Jan 27, 2016

@tkelman Are more tests needed here?

@tkelman
Copy link
Contributor

tkelman commented Jan 27, 2016

Yes, aside from the clarification about "attach" each of the differences I highlighted means a missing corner case that should be tested against (and would fail until fixed).

call{Tv,Ti}(::TrilFunc, i::Ti, j::Ti, x::Tv, k::Integer) = i + k >= j
call{Tv,Ti}(::TriuFunc, i::Ti, j::Ti, x::Tv, k::Integer) = j >= i + k
function tril!(A::SparseMatrixCSC, k::Integer = 0)
if k >= A.n - 1
Copy link
Contributor

Choose a reason for hiding this comment

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

the old boundserror conditions were, effectively, k > n || -k > m

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you prefer the old checks? I would be happy with either. The new checks are what I came up with in vacuum on the basis of logical consistency. I imagine you saw my comment above?

Added bounds checking on k. When adding tests for said bounds checks, I noticed (by way of existing tests) that the old checks may not be as tight as the new checks and throw BoundsErrors rather than ArgumentErrors. Thoughts on the new checks? Thanks!

edit: Sorry. Ignore this. Posted prior to seeing your response below. Thanks!

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 27, 2016

each of the differences I highlighted means a missing corner case that should be tested against

Added tests for these edge cases. Thanks!

@tkelman tkelman removed the needs tests Unit tests are required for this change label Jan 27, 2016
@tkelman
Copy link
Contributor

tkelman commented Jan 27, 2016

ref

if (k > 0 && k > n) || (k < 0 && -k > m)
for the current bounds check conditions on dense tril! - if we're going to offset this check by one (or two?), we should be consistent between dense and sparse.

edit: may as well make the error message consistent as well

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 27, 2016

we should be consistent between dense and sparse.

Nice catch! Shall fix.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 27, 2016

Actually, the k > n || -k > m test seems inconsistent with the associated error message? An m-by-n matrix has only n-1 superdiagonals and m-1 subdiagonals, so k == n and k == -m are already out of bounds. Hence the checks I wrote a priori. Am I missing something? If not, perhaps it makes more sense to revise the existing k > n || -k > m tests? Thanks!

@tkelman
Copy link
Contributor

tkelman commented Jan 27, 2016

Our last few comments have been crossing paths. It does probably make sense to offset the current dense checks by one (though would need to check all the other tril[!] and triu[!] methods too), but at the very least in a separate commit. And the asymmetry of having one check inclusive and the other exclusive is maybe questionable.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 28, 2016

And the asymmetry of having one check inclusive and the other exclusive is maybe questionable.

Agreed. I would be happy to symmetrize the checks. My reasoning for the asymmetric checks was as follows: tril!(A, A.n - 1) and triu!(A, 1 - A.m) are expensive identities and hence most likely logical errors, whereas tril!(A, 1 - A.m) and triu!(A, A.n - 1) are legitimate (assuming I did not mix that up).

Our last few comments have been crossing paths. It does probably make sense to offset the current dense checks by one (though would need to check all the other tril[!] and triu[!] methods too), but at the very least in a separate commit.

Cheers, proposal for action on my part: Keep the offset checks (symmetric, asymmetric, whichever you prefer) in this pull request. Open an issue regarding consistency of tri(u|l)[!] k-checks. On consensus regarding desired behavior, create a new pull request revising either the methods from this pull request or the other, presently-existing methods in accord with consensus. Thoughts? Thanks again!

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 28, 2016

Travis x86_64 unhappiness seemingly unrelated?

@tkelman
Copy link
Contributor

tkelman commented Jan 28, 2016

are expensive identities and hence most likely logical errors

I don't think it's our job to guess what the user's intent was. It may be a silly thing to ask for, but it's valid and within the bounds of the array so I don't think an exception is called for if you ask for the entire array.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 28, 2016

Checks symmetrized. Thanks! Edit: And fixed tests.

@Sacha0 Sacha0 force-pushed the fkeepvrienden branch 2 times, most recently from 7101000 to 58a6799 Compare January 28, 2016 05:17
@tkelman
Copy link
Contributor

tkelman commented Jan 28, 2016

32 bit travis failure is a new one, looks unrelated though:

julia: /home/travis/build/JuliaLang/julia/src/gc.c:607: find_region: Assertion `maybe && "find_region failed"' failed.

    From worker 3:       * docs                 Worker 3 terminated.

ERROR (unhandled task failure): EOFError: read end of file

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 28, 2016

AppVeyor i686 failures also seem unrelated?

@Sacha0
Copy link
Member Author

Sacha0 commented Feb 24, 2016

Rebased. Is this in shape to merge? Did I miss anything above? Thanks! (Prompted by #14798 (comment).)

@tkelman
Copy link
Contributor

tkelman commented Feb 24, 2016

This probably doesn't need to use functors any more?

@Sacha0
Copy link
Member Author

Sacha0 commented Feb 24, 2016

This probably doesn't need to use functors any more?

True, and likewise with sparse! and children and qftranspose! and children :). If acceptable, I would prefer to merge this pull request and #14798 as they are (with established good performance), and later carefully clean up all those methods in one shot. Does that sound good? Thanks!

@tkelman
Copy link
Contributor

tkelman commented Feb 24, 2016

Yeah that makes sense.


immutable TrilFunc <: Base.Func{4} end
immutable TriuFunc <: Base.Func{4} end
call{Tv,Ti}(::TrilFunc, i::Ti, j::Ti, x::Tv, k::Integer) = i + k >= j
Copy link
Contributor

Choose a reason for hiding this comment

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

oh, but call is now deprecated

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 can put the functors-to-functions PR together in the not-too-distant future, certainly within the deprecation cycle. Would that be alright? Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

This can still use functors, but it should do so using the non-deprecated syntax to avoid introducing warnings to the tests. The tests should currently be running with --depwarn=error on CI so I'm surprised this didn't fail.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the pointer; I was unaware of the new syntax. Assuming I found the right issues, this should be fixed now (?). Thanks!

…tril!, triu!, droptol!, and dropzeros[!] with MIT-licensed versions. See JuliaLang#13001 and JuliaLang#14631. Also add a test for dropzeros!.
@Sacha0
Copy link
Member Author

Sacha0 commented Feb 25, 2016

The Appveyor i686 discontent (failure on 7f845ff/test/arrayops.jl#L1060) seems unrelated?

tkelman added a commit that referenced this pull request Feb 26, 2016
MIT-licensed SparseMatrixCSC fkeep! and children
@tkelman tkelman merged commit 31e041e into JuliaLang:master Feb 26, 2016
@KristofferC
Copy link
Sponsor Member

Nice!

@Sacha0
Copy link
Member Author

Sacha0 commented Feb 26, 2016

@tkelman Much thanks for the thorough review here as well!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
potential benchmark Could make a good benchmark in BaseBenchmarks sparse Sparse arrays
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants