-
Notifications
You must be signed in to change notification settings - Fork 46
Preserve ranges in indexing with IdentityUnitRange(::Base.OneTo) #211
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
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
| 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 |
|
|
||
| @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)
falseThere 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!
timholy
left a comment
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.
|
|
||
| @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
AbstractRanges 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
OffsetArrayspirates theBasebehavior 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 anAbstractRangeas opposed to anOffsetVector.