-
-
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
RFC: Make LinearIndices consistent with linearindices for vectors #26682
Conversation
I think This seems like the right thing to do. |
base/multidimensional.jl
Outdated
@@ -418,6 +419,7 @@ module IteratorsMD | |||
@inbounds result = reshape(1:prod(dims), dims)[(I .- first.(iter.indices) .+ 1)...] | |||
return result | |||
end | |||
@inline Base.getindex(iter::LinearIndices{1,R}, i::Int) where {R} = i |
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.
Is it really true that this definition could apply to all LinearIndices
and not just LinearIndices{1}
?
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.
It's only used for LinearIndices
here. Do you suggest it could work for any dimensionality, i.e. that we could remove the more general method above?
Regarding switching to IndexLinear
, I'm concerned about performance, given the comment that @timholy has put in the method above. I've tested it, and without bounds checks this test fails:
Line 121 in c4c93ea
@test_throws BoundsError LinearIndices(i)[2] |
Given that checking bounds against the tuple of axes is going to be slow in the multidimensional case, maybe we need to keep IndexCartesian
? I must say I don't understand where the bounds are checked currently since the method above uses @inbounds
.
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, that method is sketchy — it's not checking bounds at all. I'm proposing:
shell> git diff
diff --git a/base/multidimensional.jl b/base/multidimensional.jl
index 54124ed0b3..d56386b88b 100644
--- a/base/multidimensional.jl
+++ b/base/multidimensional.jl
@@ -410,16 +410,13 @@ module IteratorsMD
LinearIndices(A::AbstractArray) = LinearIndices(CartesianIndices(A))
# AbstractArray implementation
- Base.IndexStyle(::Type{LinearIndices{N,R}}) where {N,R} = IndexCartesian()
+ Base.IndexStyle(::Type{LinearIndices{N,R}}) where {N,R} = IndexLinear()
Base.axes(iter::LinearIndices{N,R}) where {N,R} = iter.indices
Base.size(iter::LinearIndices{N,R}) where {N,R} = length.(iter.indices)
- @inline function Base.getindex(iter::LinearIndices{N,R}, I::Vararg{Int, N}) where {N,R}
- dims = length.(iter.indices)
- #without the inbounds, this is slower than Base._sub2ind(iter.indices, I...)
- @inbounds result = reshape(1:prod(dims), dims)[(I .- first.(iter.indices) .+ 1)...]
- return result
+ @inline function Base.getindex(iter::LinearIndices, i::Int)
+ @boundscheck checkbounds(iter, i)
+ i
end
- @inline Base.getindex(iter::LinearIndices{1,R}, i::Int) where {R} = i
end # IteratorsMD
Amazingly, it seems to have identical performance comparing srand(0); @btime L[i,j,k] setup=(i=rand(1:3);j=rand(1:4);k=rand(1:5))
with L = LinearIndices(rand(3,4,5))
. And it fixes this case:
# This PR + my patch above:
julia> L = LinearIndices(rand(3,4,5));
julia> srand(0); @btime L[i,j,k] setup=(i=rand(1:3);j=rand(1:4);k=rand(1:5))
31.524 ns (0 allocations: 0 bytes)
22
julia> L[3,4,6]
ERROR: BoundsError: attempt to access 3×4×5 LinearIndices{3,Tuple{Base.OneTo{Int64},Base.OneTo{Int64},Base.OneTo{Int64}}} at index [3, 4, 6]
# A 7-day old master: bf45de8c33 (2018-03-26 16:43 UTC)
julia> srand(0); @btime L[i,j,k] setup=(i=rand(1:3);j=rand(1:4);k=rand(1:5))
31.048 ns (0 allocations: 0 bytes)
22
julia> L[3,4,6]
72
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, that's what I had in mind too. Glad to hear it doesn't affect performance. I've updated the PR.
Change LinearIndices(x::AbstractVector) to use the same indices as axes(x, 1). This is consistent with linearindices(x) and allows indexing into x. Change CartesianIndices to use custom axes so that it works with indices returned by LinearIndices for vectors.
5739db2
to
e87817b
Compare
base/multidimensional.jl
Outdated
Base.IndexStyle(::Type{CartesianIndices{N,R}}) where {N,R} = IndexCartesian() | ||
@inline Base.getindex(iter::CartesianIndices{N,R}, I::Vararg{Int, N}) where {N,R} = CartesianIndex(first.(iter.indices) .- 1 .+ I) | ||
@inline Base.getindex(iter::CartesianIndices{N,R}, I::Vararg{Int, N}) where {N,R} = CartesianIndex(I) |
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.
Actually, on second thought — we need to add @boundscheck checkbounds(iter, I)
to this implementation, too. Now's the time to do it since this is definitely a performance gain right now.
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.
Makes sense. I've added a commit (AFAICT that's checkbounds(iter, I...)
).
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.
Thanks!
Um, can someone explain the change to julia> A=rand(5,5);
julia> A[CartesianIndices((2:3, 3:5))]
ERROR: MethodError: no method matching similar(::Array{Float64,2}, ::Type{Float64}, ::Tuple{UnitRange{Int64},UnitRange{Int64}}) Is that intended? (If I understand correctly, this tries to return an array for indices
Of course, |
Dang, that's unfortunate. Yes, your understanding is correct. The properties I'm striving for here are:
… although now upon closer examination it looks like that third one is still broken for OffsetArrays. |
These things are so tricky... What's the use case for indexing an array with a Regarding the failure of include("test/TestHelpers.jl")
A = TestHelpers.OAs.OffsetArray([1 4; 2 3], (3,2))
CartesianIndices(A)[1:4]
Base._unsafe_getindex!(collect(CartesianIndices(A)), CartesianIndices(A), 1:4, 1:4) It appears that |
I'm not sure; I just stumbled upon this because it broke a test in Compat.jl (ref JuliaLang/Compat.jl#529 / JuliaLang/Compat.jl@956bda9). I think the problem is that
Note that we currently don't have anything for the lower right entry. Not that I have a use case, but it leaves me a bit uneasy to leave a gap there. Also note that |
Good summary. All of this comes from the fact that we cannot distinguish linear from cartesian indices when indexing into vectors, so linear indices do not always start at 1. Maybe we need a separate |
The current status makes indexing with |
The more important part is that we can use these objects to convert between linear and cartesian indices. There's an alternative here that I thought would be more disruptive, but we could give it a shot: have |
IIUC, that would make |
Would a resolution here potentially be breaking? (I.e., do we need to hurry?) |
Yes, it would. I just started on exploring potential solutions yesterday afternoon. |
Change
LinearIndices(x::AbstractVector)
to use the same indices asaxes(x, 1)
. This is consistent withlinearindices(x)
and allows indexing intox
.Change
CartesianIndices
to use custom axes so that it works with indices returned byLinearIndices
for vectors.This matches the description of linear indices in the manual, and should allow deprecating
linearindices
in favor ofLinearIndices
if we want. See #25901 (comment).If we want this PR, docstrings could be improved to be more explicit.
Before:
After: