-
-
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
Speed up permsort by utilizing stability of the default sorting algorithm #47587
base: master
Are you sure you want to change the base?
Changes from 36 commits
42c70a1
61e4006
901182c
e032ba6
e6cfee0
f160582
15a4484
d82b090
029cbae
d3bdca3
2232cac
a574c7f
05de36e
70290d6
f06de10
38f4512
383b9d2
d8ae968
c633419
32a6f54
a2c2646
71e8fa1
812c917
e752ea7
15666f2
04399d9
34621c7
7e6f103
77b2b08
36d3ff3
1fe68d9
dd1d89b
91c2d2a
20ddeb4
c14432b
176d779
a2f9710
2d2cf4d
ef8e8eb
3b972eb
9b5be34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -560,15 +560,52 @@ elements that are not | |
@inline send_to_end!(f::F, v::AbstractVector, ::ReverseOrdering, end_stable=false; lo, hi) where F <: Function = | ||
end_stable ? (send_to_end!(!f, v; lo, hi)+1, hi) : (hi-send_to_end!(f, view(v, hi:-1:lo))+1, hi) | ||
|
||
""" | ||
send_to_end_stable!(f::Function, v::AbstractVector; [lo, hi]) | ||
|
||
Send every element of `v` for which `f` returns `true` to the end of the vector and return | ||
the index of the last element which for which `f` returns `false`. | ||
|
||
`send_to_end_stable!(f, v, lo, hi)` is equivalent to `send_to_end_stable!(f, view(v, lo:hi))+lo-1` | ||
|
||
Preserves the order of the elements. | ||
""" | ||
function send_to_end_stable!(f::F, v::AbstractVector; lo=firstindex(v), hi=lastindex(v)) where F <: Function | ||
tmp=copy(v) | ||
offset = 0 | ||
@inbounds begin | ||
while lo <= hi | ||
x = tmp[lo] | ||
fx = f(x)::Bool | ||
v[(fx ? hi : lo) - offset] = x | ||
offset += fx | ||
lo += 1 | ||
end | ||
end | ||
|
||
# This is similar to the partition function | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to have code re-use; the loop here is almost exactly the same as the loops in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice. However, |
||
pivot_index = lo-offset-1 | ||
# t[<=pivot_index] <* f(x) = false | ||
# t[>pivot_index] >* f(x) = true | ||
|
||
# Make the results stable | ||
reverse!(v, pivot_index+1, hi) | ||
return pivot_index | ||
end | ||
|
||
@inline send_to_end_stable!(f::F, v::AbstractVector, ::ForwardOrdering; lo, hi) where F <: Function = (lo, send_to_end_stable!(f, v; lo, hi)) | ||
@inline send_to_end_stable!(f::F, v::AbstractVector, ::ReverseOrdering; lo, hi) where F <: Function = (hi-send_to_end_stable!(f, view(v, hi:-1:lo))+1, hi) | ||
|
||
function _sort!(v::AbstractVector, a::MissingOptimization, o::Ordering, kw) | ||
@getkw lo hi | ||
if nonmissingtype(eltype(v)) != eltype(v) && o isa DirectOrdering | ||
lo, hi = send_to_end!(ismissing, v, o; lo, hi) | ||
_sort!(WithoutMissingVector(v, unsafe=true), a.next, o, (;kw..., lo, hi)) | ||
elseif eltype(v) <: Integer && o isa Perm{DirectOrdering} && nonmissingtype(eltype(o.data)) != eltype(o.data) | ||
elseif eltype(v) <: Integer && (o isa Perm{DirectOrdering} || o isa PermFast{DirectOrdering}) && | ||
nonmissingtype(eltype(o.data)) != eltype(o.data) | ||
PermT = o isa Perm{DirectOrdering} ? Perm : PermFast | ||
petvana marked this conversation as resolved.
Show resolved
Hide resolved
|
||
lo, hi = send_to_end!(i -> ismissing(@inbounds o.data[i]), v, o) | ||
_sort!(v, a.next, Perm(o.order, WithoutMissingVector(o.data, unsafe=true)), (;kw..., lo, hi)) | ||
_sort!(v, a.next, PermT(o.order, WithoutMissingVector(o.data, unsafe=true)), (;kw..., lo, hi)) | ||
else | ||
_sort!(v, a.next, o, kw) | ||
end | ||
|
@@ -603,7 +640,7 @@ function _sort!(v::AbstractVector, a::IEEEFloatOptimization, o::Ordering, kw) | |
j = send_to_end!(x -> after_zero(o, x), v; lo, hi) | ||
scratch = _sort!(iv, a.next, Reverse, (;kw..., lo, hi=j)) | ||
if scratch === nothing # Union split | ||
_sort!(iv, a.next, Forward, (;kw..., lo=j+1, hi, scratch)) | ||
_sort!(iv, a.next, Forward, (;kw..., lo=j+1, hi, nothing)) | ||
else | ||
_sort!(iv, a.next, Forward, (;kw..., lo=j+1, hi, scratch)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @LilithHafner Btw, is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recall that removing the if statement entirely introduced dynamic dispatch. IICU, all of type inference is an implementation detail, so I'm not totally sure, but I believe that we need an if statement to force union splitting though it works well whether we use |
||
end | ||
|
@@ -613,10 +650,20 @@ function _sort!(v::AbstractVector, a::IEEEFloatOptimization, o::Ordering, kw) | |
j = send_to_end!(i -> after_zero(o.order, @inbounds o.data[i]), v; lo, hi) | ||
scratch = _sort!(v, a.next, Perm(Reverse, ip), (;kw..., lo, hi=j)) | ||
if scratch === nothing # Union split | ||
_sort!(v, a.next, Perm(Forward, ip), (;kw..., lo=j+1, hi, scratch)) | ||
_sort!(v, a.next, Perm(Forward, ip), (;kw..., lo=j+1, hi, nothing)) | ||
else | ||
_sort!(v, a.next, Perm(Forward, ip), (;kw..., lo=j+1, hi, scratch)) | ||
end | ||
elseif eltype(v) <: Integer && o isa PermFast && o.order isa DirectOrdering && is_concrete_IEEEFloat(eltype(o.data)) | ||
lo, hi = send_to_end_stable!(i -> isnan(@inbounds o.data[i]), v, o.order; lo, hi) | ||
ip = reinterpret(UIntType(eltype(o.data)), o.data) | ||
j = send_to_end_stable!(i -> after_zero(o.order, @inbounds o.data[i]), v; lo, hi) | ||
scratch = _sort!(v, a.next, PermFast(Reverse, ip), (;kw..., lo, hi=j)) | ||
if scratch === nothing # Union split | ||
_sort!(v, a.next, PermFast(Forward, ip), (;kw..., lo=j+1, hi, nothing)) | ||
else | ||
_sort!(v, a.next, PermFast(Forward, ip), (;kw..., lo=j+1, hi, scratch)) | ||
end | ||
petvana marked this conversation as resolved.
Show resolved
Hide resolved
|
||
else | ||
_sort!(v, a.next, o, kw) | ||
end | ||
|
@@ -1558,7 +1605,11 @@ function sortperm(A::AbstractArray; | |
end | ||
end | ||
ix = copymutable(LinearIndices(A)) | ||
sort!(ix; alg, order = Perm(ordr, vec(A)), scratch, dims...) | ||
if alg == DEFAULT_STABLE | ||
sort!(ix; alg, order = PermFast(ordr, vec(A)), scratch, dims...) | ||
else | ||
sort!(ix; alg, order = Perm(ordr, vec(A)), scratch, dims...) | ||
end | ||
end | ||
|
||
|
||
|
@@ -1615,7 +1666,11 @@ function sortperm!(ix::AbstractArray{T}, A::AbstractArray; | |
if !initialized | ||
ix .= LinearIndices(A) | ||
end | ||
sort!(ix; alg, order = Perm(ord(lt, by, rev, order), vec(A)), scratch, dims...) | ||
if alg == DEFAULT_STABLE | ||
sort!(ix; alg, order = PermFast(ord(lt, by, rev, order), vec(A)), scratch, dims...) | ||
else | ||
sort!(ix; alg, order = Perm(ord(lt, by, rev, order), vec(A)), scratch, dims...) | ||
end | ||
end | ||
|
||
# sortperm for vectors of few unique integers | ||
|
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.
Ideally, this should utilize the scratch space re-use system, though it this PR is a performance improvement without, then that isn't essential.
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've tried to re-use the scratch array, but not sure if i got your
@getkw
concept right.