-
-
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
Optimized unique and union! methods #15009
Conversation
NOTE: The build generates warnings about ambiguous methods along the lines of WARNING: New definition
union!(Base.Set, Base.LinAlg.Tridiagonal{#T<:Any}) at linalg/tridiag.jl:495
is ambiguous with:
union!(Base.Set{Int8}, AbstractArray{Int8, N<:Any}) at set.jl:64.
To fix, define
union!(Base.Set{Int8}, Base.LinAlg.Tridiagonal{Int8})
before the new definition. On my system the right method seems to be called anyway julia> A = Diagonal(Int8[1,2,3,4])
4x4 Diagonal{Int8}:
1 ⋅ ⋅ ⋅
⋅ 2 ⋅ ⋅
⋅ ⋅ 3 ⋅
⋅ ⋅ ⋅ 4
julia> @which union!(Set(),A)
union!{T}(s::Set{T<:Any}, D::Diagonal{T}) at linalg/diagonal.jl:240 but it would be preferable to get rid of these warnings. As far as I can tell the recommended approach would require duplicating the functionality for collections with small eltypes to all the structured array types. I would like to avoid this and was hoping to find suggestions to work around it. |
From a brief skim, this looks amazingly thorough. I'm not sure I understand the reason for all the constructs like As far as the ambiguity warning goes: dispatching on both the element type and the container type is often a formula for trouble. I'm not sure which should take precedence, but let's suppose it's the container type. Then rather than "sharing an implicit common node" (which is what the ambiguity warning is suggesting you define), I think a better approach is to cascade to differently-named methods: ## Split on container type
function union!(s::Set, x::Diagonal)
# code to handle Diagonal
end
function union!(s::Set, Tridiagonal)
# code to handle Tridiagonal
end
union!(s::Set, x::AbstractArray) = _union!(s, x) # fallback method
## Split on element type
function _union!{T<:SpecialElementTypes}(s::Set{T}, x::AbstactArray{T})
...
end
function _union!(s::Set, x::AbstractArray)
# the generic method
...
end (Stated otherwise, from a given node in the method "tree," split on either container type OR element type, not both simultaneously.) |
EDIT: I gave a very confusing answer and so thought it best to write a new one The most straightforward way to The main problem with the above "straightforward" method is that is essentially requires reimplementing all the logic of the generic So the idea behind the implementation in the pull request is to form a new matrix that only contains the data of the nonzero regions in the correct (column major) order plus one or more zero elements in appropriate places so that the result of calling generic This perhaps best explained with an example:
This approach allows for zeros to be found before the structured zero portion of the matrix, but without the need to branch upon their detection. Furthermore, in the case that the matrix has small eltype, the intermediate matrix |
@timholy not sure I fully grok the cascading method approach, but I'll give it a try. |
s | ||
end | ||
function unique{Tv,Ti<:Integer}(x::SparseVector{Tv,Ti}) | ||
inds = sort(x.nzind) |
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.
generally these should always be sorted as part of the data structure convention
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.
Got it, this fact was documented for SparseMatrixCSC but I didn't see it for SparseVectors. I may elect to take the liberty of adding this to comments.
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.
yes, please do
could use some inline comments explaining the approach and constructions used. do these work for the case of 1x1 matrices of these types? |
@@ -786,3 +786,5 @@ end | |||
|
|||
in{T<:Integer}(x, r::Range{T}) = isinteger(x) && !isempty(r) && x>=minimum(r) && x<=maximum(r) && (mod(convert(T,x),step(r))-mod(first(r),step(r)) == 0) | |||
in(x::Char, r::Range{Char}) = !isempty(r) && x >= minimum(r) && x <= maximum(r) && (mod(Int(x) - Int(first(r)), step(r)) == 0) | |||
|
|||
unique(x::Range) = x |
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.
Maybe this should return an Array
instead (i.e. call collect
)? The documentation for unique
isn't very clear about what kind of "array" it returns. Not sure.
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 was the main idea of #14649.
IMO unique needn't always be an array, if the datastructure is already unique e.g. Set/Range/etc. then a no-op should be preferred.
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'm fine with that. But note that sets are applied collect
, as they are not AbstractArrays
.
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 don't think unique on Sets need collect, and return an AbstractArray, that was also in 14649. Or maybe I'm misunderstanding you.
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.
All I'm saying is that this is what happens with this PR, and that this sounds justified, as Set
s are not AbstractArray
s and e.g. cannot be indexed.
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.
Isn't this a concern?
_unique(x::Range) = x
length(unique(linspace(0,0,10)))
1
length(_unique(linspace(0,0,10)))
10
I was under the impression that Range
s are not in general unique.
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 was brought up in #14649. I had thought that the above behavior behavior was supposed to be a bug but reading back on the comments it looks like its still ambiguous. This PR has lingered on a bit long, so I modified the method to accommodate either case. WRT to the larger issue of confusion about range with zero step sizes see #15218.
@gajomi, I edited my post above to hopefully make the intention clearer. Here's a little diagram that might help: "Product-specialization" is the solution that's recommended in the ambiguity warning, but over time I've found that it's rarely a good idea to follow that advice. The "cascade" model is the one I usually find more productive. |
Thanks everyone for the extensive comments. I have mostly fixed things up and as a natural result of the process of refactoring have slightly expanded the situations in which the optimization apply, so I'll have to think a bit about upgrading benchmarks. |
fd2d12f
to
300a496
Compare
OK, this took quite a bit longer than I expected (there were many forks in the road I'll to revisit later), but the latest commits should address all the issues raised above. New performance benchmarks can be found here: https://gist.github.com/gajomi/617466a1502dbf920252/revisions?diff=split Differences from first commits:
|
300a496
to
a8a63a8
Compare
It seems that a test associated with @test union!(IntSet(UInt8[]),[collect(0x00:0xff); 0x01]) == IntSet(collect(0x00:0xff)) This gave a deprecation warning about pushing zeros into |
return unique(hcat([dv[1],ev[1]], | ||
zeros(T,2), | ||
[dv[2:end-1] ev[2:end]]', | ||
[zero(T),dv[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.
can you add a few more comments on 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.
Yeah I added some comments (and ended up refactoring lower branch)/
a8a63a8
to
7d5a4f6
Compare
7d5a4f6
to
44e275c
Compare
This seems to be good to go (modulo some potentially needed fixups stemming from nearly orthogonal issue discussed at JuliaLang/LinearAlgebra.jl#310). |
end | ||
union!(s::AbstractSet, xs) = _union!(s, xs) | ||
|
||
_union!(s::AbstractSet, xs) = rawunion!(s, xs) |
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.
what is the purpose of having the extra step 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.
union!
for Set
types needs a size hint before calling rawunion!
, whereas in IntSet
one gets a size hint for every push!
(as the underlying container size depends on largest value).
I'm gonna call this one stale. As a general comment, it seemed like this PR combined quite a number of different changes. Generally it's easier to get things in if they are done if very small pieces. New API surface in particular usually is quite slow because it's hard to change later. That said, if anybody ever wants to pick this up again, you're welcome to of course. |
This PR implements performance improvements for unique and union! and adds an additional method signature for unique. The basic outline of the different performance issues can be found in #14649. There are four separates commits in the pull request which implement
Each of these optimizations have different regimes in which performance improvements become apparent. A benchmark that illustrates performance improvements for unique in action can be seen by looking at the diff in this gist: https://gist.github.com/gajomi/0c184160042ddfc329ab/revisions (results similar for union!). Generally speaking, one sees improvements in walltime performance, although there are some minor performance regressions in memory allocation for some methods.