-
Notifications
You must be signed in to change notification settings - Fork 40
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
Preserve ranges in indexing with IdentityUnitRange(::Base.OneTo) #211
Conversation
Codecov Report
@@ Coverage Diff @@
## master #211 +/- ##
==========================================
+ Coverage 94.57% 94.69% +0.11%
==========================================
Files 5 5
Lines 332 358 +26
==========================================
+ Hits 314 339 +25
- Misses 18 19 +1
Continue to review full report at Codecov.
|
0411947
to
7559b67
Compare
Just to clarify, this is breaking only in the sense that different types are returned, right? As you know, I'm generally in favor of thinking of types as an implementation detail so that kind of "breakage" is OK if it doesn't really change behavior. |
Yes this only changes the type of the returned object, there is no change in the axes or the values. While this is not breaking in that sense, it might cause some overly-specialized functions to falter. Perhaps it's better to be conservative and make this a part of 1.7 while #210 can go into 1.6.x? In general it seems like a good strategy to push changes in types to minor releases. This way a compat entry that restricts the minor version would receive (mostly) non-breaking patches. There might be some permissible breakage in minor releases due to changes that are not considered a part of the API, whereas API-breaking changes go to major releases. This avoids patch releases breaking something as in #163 |
08c17f3
to
fcbfbba
Compare
The latest change in da226f7 is another step in preserving As a consequence, before: julia> a = ones(200);
julia> r = OffsetVector(2:500, 2);
julia> s = OffsetVector(3:150, 3);
julia> @btime $a[$r[$s]];
639.861 ns (1 allocation: 1.33 KiB) Now: julia> @btime $a[$r[$s]];
356.967 ns (1 allocation: 1.33 KiB) Some of the tests had to be made a bit less strict, as we need to test for axes and values and not necessarily for types. Edit: well this isn't working anymore, now |
Main change introduced in c3ef299: Previously: julia> a = ones(200);
julia> s = OffsetVector(3:150, 3);
julia> @btime $a[$(parent(s))];
205.697 ns (1 allocation: 1.33 KiB)
julia> @btime $a[$s];
638.503 ns (1 allocation: 1.33 KiB) After this julia> @btime $a[$s];
303.116 ns (1 allocation: 1.33 KiB) The indexing performance becomes comparable to that of the parent |
We're back to julia> @btime $a[$r[$s]];
358.557 ns (1 allocation: 1.33 KiB)
julia> @btime $a[$(r[s])];
346.858 ns (1 allocation: 1.33 KiB) The benchmark for Without julia> a = ones(2000);
julia> s = OffsetVector(axes(a,1), 0);
julia> @btime $a[$s];
6.659 μs (1 allocation: 15.75 KiB) Without julia> @btime $a[$s];
3.735 μs (1 allocation: 15.75 KiB) With both: julia> @btime $a[$s];
2.848 μs (1 allocation: 15.75 KiB) The difference after adding |
vectorindexing(a, s) = a[s] | ||
nestedvectorindexing(a, r, s) = a[r[s]] | ||
|
||
macro showbenchmark(ex) |
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 is added to avoid cluttering the display with line numbers
|
||
#= Integer UnitRanges may return an appropriate AbstractUnitRange{<:Integer}, as the result may be used in indexing, and | ||
indexing is faster with ranges =# | ||
@eval @propagate_inbounds function Base.getindex(r::UnitRange{<:Integer}, s::$OR) |
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 is not necessary anymore as _maybewrapoffset
already handles this case
@@ -72,10 +72,10 @@ function _checkindices(N::Integer, indices, label) | |||
N == length(indices) || throw_argumenterror(N, indices, label) | |||
end | |||
|
|||
_maybewrapaxes(A::AbstractVector, ::Base.OneTo) = no_offset_view(A) |
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.
_maybewrapaxes
is effectively equivalent to _maybewrapoffset
, so there's no need for this
@test axes(r1) == (IdentityUnitRange(-5:-4),) | ||
@test parent(r1) === 8:9 | ||
@test axes(r1) == (-5:-4,) | ||
@test no_offset_view(r1) == 8:9 |
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 indexing operation returns an IdOffsetRange
now, so the parent might not be equal to the expected range. We check the values instead using no_offset_view
and the axes separately
There is still some performance hit in Cartesian indexing: julia> a = ones(2000, 2000);
julia> s = OffsetVector(axes(a,1), 0);
julia> @btime $a[$(parent(s)), $(parent(s))]; # no offset, so this is fast
7.548 ms (2 allocations: 30.52 MiB)
julia> @btime $a[$(parent(s)), $s]; # not sure why this is fast but the others aren't?
7.586 ms (2 allocations: 30.52 MiB)
julia> @btime $a[$s, $(parent(s))];
7.841 ms (2 allocations: 30.52 MiB)
julia> @btime $a[$s, $s];
7.875 ms (5 allocations: 30.52 MiB)
julia> sior = IdOffsetRange(parent(s));
julia> @btime $a[$sior, $sior]; # this is fast again
7.480 ms (2 allocations: 30.52 MiB) |
Inlining Now julia> @btime $a[$(parent(s)), $(parent(s))];
6.755 ms (2 allocations: 30.52 MiB)
julia> @btime $a[$s, $s];
6.718 ms (5 allocations: 30.52 MiB) However, oddly it seems nested linear indexing is faster if julia> a = ones(2000);
julia> r = OffsetVector(1:2000, 0);
julia> s = OffsetVector(axes(a,1), 0);
julia> @btime $a[$r[$s]]; # r[s] Returns an OffsetRange
3.465 μs (1 allocation: 15.75 KiB)
julia> @btime $a[$r[$s]]; # r[s] Returns an IdOffsetRange
4.331 μs (1 allocation: 15.75 KiB) I'm not sure what causes this hit, but in any case |
I think we're almost there after 72a5eaf. Now julia> a = ones(2000);
julia> r = OffsetVector(1:2000, 0);
julia> s = OffsetVector(axes(a,1), 0);
julia> @btime $a[$r[$s]];
2.103 μs (1 allocation: 15.75 KiB)
julia> @btime $a[$s];
2.067 μs (1 allocation: 15.75 KiB) To summarize (timings on Julia 1.5.3): Using julia> a = ones(2000); b = ones(1:2000); c = ones(2000, 2000);
julia> r = OffsetVector(1:2000, 0);
julia> s = OffsetVector(axes(a,1), 0);
|
5c19969
to
c6af893
Compare
Worth waiting for JuliaLang/julia#39896 to restrict some of these methods to older versions of Julia |
const IIUR = IdentityUnitRange{S} where S<:AbstractUnitRange{T} where T<:Integer | ||
|
||
Base.step(a::OffsetRange) = step(parent(a)) | ||
|
||
@propagate_inbounds function Base.getindex(a::OffsetRange, r::OffsetRange) | ||
OffsetArray(a.parent[r.parent .- a.offsets[1]], axes(r)) | ||
Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, or::OffsetRange) = Base.checkindex(Bool, inds, parent(or)) |
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 doesn't seem correct:
julia> ax = OffsetArrays.IdOffsetRange(Base.OneTo(5), -3)
OffsetArrays.IdOffsetRange(values=-2:2, indices=-2:2)
julia> axes(ax) == axes(ax.parent)
false
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.
Not sure I understand. checkindex
does not use the axes, it checks that the first and last values are contained in inds
, and the first and last values of an OffsetRange
should be the same as those of its parent. It's an OffsetRange
btw (which is an OffsetArray
wrapped around an AbstractRange
), and not an IdOffsetRange
.
Perhaps I'm missing something, could you expand on that example to illustrate the problem?
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.
You're right, sorry!
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.
Sorry for the delay, LGTM. I like limiting the number of methods where we can, so making it version-dependent seems like a great idea.
const IIUR = IdentityUnitRange{S} where S<:AbstractUnitRange{T} where T<:Integer | ||
|
||
Base.step(a::OffsetRange) = step(parent(a)) | ||
|
||
@propagate_inbounds function Base.getindex(a::OffsetRange, r::OffsetRange) | ||
OffsetArray(a.parent[r.parent .- a.offsets[1]], axes(r)) | ||
Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, or::OffsetRange) = Base.checkindex(Bool, inds, parent(or)) |
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.
You're right, sorry!
0951109
to
a8df668
Compare
This PR changes the type in indexing
AbstractRange
s withIdentityUnitRange(::Base.OneTo)
. This is technically breaking so I've separated this from #210 . This may go into a minor release.The reason this PR is necessary is that currently
OffsetArrays
pirates theBase
behavior and changes the return type (although not the values/axes).After #210:
After this PR:
Despite the piracy, the indexing operation returns the same type as
Base
. Importantly this returns anAbstractRange
as opposed to anOffsetVector
.