-
-
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
improve performance of searchsorted(range, lt=<) #50365
Conversation
I think a dispatch-based approach would be more idiomatic, concise, and extensible, unless there are performance issues: _ord(lt::typeof(isless), by, order::Ordering) = _by(by, order)
_ord(lt, by, order::ForwardOrdering) = _by(by, _lt(lt))
_ord(lt, by, order::ReverseOrdering) = reverse(_by(by, _lt(lt)))
_ord(lt, by, order::Ordering) =
error("Passing both lt= and order= arguments is ambiguous; please pass order=Forward or order=Reverse (or leave default)")
_by(by, order::Ordering) = By(by, order)
_by(::typeof(identity), order) = order
_lt(lt) = Lt(lt)
_lt(::typeof(isless)) = ForwardOrdering |
@LilithHafner yeah I guess dispatch is cleaner, here I followed the existing code just below - it uses |
Also added tests, these options weren't really tested for ranges as I can see. |
Tests fail but failures are irrelevant, so idk how to proceed further. |
There's a whitespace test failure—that is almost always relevant |
The remaining failure is irrelevant I guess? |
Gentle bump... |
@@ -225,7 +225,10 @@ function searchsorted(v::AbstractVector, x, ilo::T, ihi::T, o::Ordering)::UnitRa | |||
return (lo + 1) : (hi - 1) | |||
end | |||
|
|||
function searchsortedlast(a::AbstractRange{<:Real}, x::Real, o::DirectOrdering)::keytype(a) | |||
|
|||
const FastRangeOrderings = Union{DirectOrdering,Lt{typeof(<)},ReverseOrdering{Lt{typeof(<)}}} |
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.
If you need a Union like DirectOrdering
, usually that is a sign that something is designed poorly, and a bit buggy in edge case. But it appears that all of these types in the Union are concrete, so it is not huge and much less likely to be buggy, but still a little bit concerning design-wise. Is this Union likely to need to grow in the future, and if so, is there something better?
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.
DirectOrdering
already exists and was used in this function signature before. Here, I extended it with two more concrete types.
DirectOrdering
is also user for fastpath in sort
, with missings and with floats/integers. Sorting missings doesn't play nicely with <
anyway, and <
on floats is complicated by NaN
s, so I guess these two unions will remain different.
I don't foresee any extension to this union. In principle, stuff like isgreater
and >
could also be supported, but there's no need given the rev
argument.
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 type gates dispatch to an optimization that relies on a monotonic order over the real numbers. It could be extended to be arbitrarily large to e.g. include By{typeof(atan), ForwardOrdering}
.
This pr optimizes
searchsorted(range, lt=<)
That further extension would optimize
searchsorted(range, by=atan)
A more general solution would be an ismonotonic
trait.
All this is a tradeoff between runtime performance on easy edge cases vs. compile times and sysimg size. It's unfortunate that it is so difficult to instrument compile time and sysimg size regressions.
bump! |
I refrained from merging because of I'm not necessarily opposed to someone else merging this, I just want to take responsibility for those hard to quantify regressions in exchange for a performance win on such a small edge case. |
Arguably, julia> searchsorted(-1000.0:1:1000, -0.0)
1001:1000
julia> searchsorted(-1000.0:1:1000, -0.0; lt=<)
1001:1001 So I think it's important to make That's exactly what this PR does. |
bump |
bump... |
Looking at this again after a while, I'm remembering that passing |
That is good input, but I think I am okay with that minor risk here |
@LilithHafner my only motivation here is to have performant search that can actually find equal values (like 0 and -0). For me, using and optimizing |
Is there any chance to have this PR to be backported to v1.10? |
No. We don't backport performance only PRs. |
No behavior change, just a performance improvement: searchsorted(range, lt=<) now performs exactly as fast as searchsorted(range).
Passing lt=< allows searchsorted to find floating point values such as -0.0, so it's useful to make it fast - see #44102 (comment).