Skip to content
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

Add Tuple(::CartesianIndex) #23719

Merged
merged 1 commit into from
Sep 20, 2017
Merged

Add Tuple(::CartesianIndex) #23719

merged 1 commit into from
Sep 20, 2017

Conversation

martinholters
Copy link
Member

@martinholters martinholters commented Sep 15, 2017

Make iterating a CartesianIndex equivalent to iterating the tuple it holds, being consistent with its indexing behavior. This is especially useful for destructuring as in e.g. i, j = indmax(A).

Just add Tuple(::CartesianIndex) as per discussion below. Destructuring now would require i, j = Tuple(indmax(A)), but it's still possible without explicitly accessing the I field.

@ararslan ararslan added the collections Data structures holding multiple items, e.g. sets label Sep 15, 2017
@timholy
Copy link
Sponsor Member

timholy commented Sep 15, 2017

Historically I've opposed this change simply as a means of avoiding a major (and unfortunately very natural) performance trap:

julia> Base.eltype(index::CartesianIndex) = eltype(index.I)

julia> Base.start(index::CartesianIndex) = start(index.I)

julia> Base.next(index::CartesianIndex, state) = next(index.I, state)

julia> Base.done(index::CartesianIndex, state) = done(index.I, state)

julia> function mysum1(A)
           s = 0.0
           @inbounds for i in CartesianRange(indices(A))
               s += A[i]
           end
           s
       end
mysum1 (generic function with 1 method)

julia> function mysum2(A)
           s = 0.0
           @inbounds for i in CartesianRange(indices(A))
               s += A[i...]
           end
           s
       end
mysum2 (generic function with 1 method)

julia> A = rand(10,10,10,10,10,10);

# after warmup (@btime is broken on master)
julia> @time mysum1(A)
  0.003628 seconds (5 allocations: 176 bytes)
500411.4240107683

julia> @time mysum2(A)
  1.641540 seconds (15.00 M allocations: 686.646 MiB, 5.57% gc time)
500411.4240107683

However, given that a version using A[i.I...] is fast, perhaps there could be accompanying changes to inference.jl that would eliminate this penalty.

@martinholters
Copy link
Member Author

Ah, yes, that's a bad trap. Though possible, I'd rather not add a special case/hack to inference. The much nicer and more general approach would be to add sufficient constant propagation to figure out cases where the length of the object to be splatted is known at compile time and then do the same inlining as for Tuples. Clearly, that is beyond the scope of this PR.

So for now, I guess we'd have to decide between convenience and avoiding a performance pitfall.

@mbauman
Copy link
Sponsor Member

mbauman commented Sep 15, 2017

Could just defining Tuple be sufficient for your purposes?

julia> Tuple(I::CartesianIndex) = I.I
Tuple

julia> function mysum2(A)
                  s = 0.0
                  @inbounds for i in CartesianRange(indices(A))
                      s += A[Tuple(i)...]
                  end
                  s
              end
mysum2 (generic function with 1 method)

julia> A = rand(10,10,10,10,10,10);

julia> @time mysum2(A)
  0.625158 seconds (222.97 k allocations: 12.000 MiB, 8.90% gc time)
500281.72646593733

julia> @time mysum2(A)
  0.008289 seconds (5 allocations: 176 bytes)
500281.72646593733

Edit: I suppose it doesn't solve the destructuring problem. :-\

@timholy
Copy link
Sponsor Member

timholy commented Sep 16, 2017

I guess my viewpoint is that since it's easy to destructure with i.I, then in the absence of improvements to inference.jl the status quo is preferable. It's just too easy to fall into that performance trap and then think the whole CartesianIndex thing is useless.

@martinholters
Copy link
Member Author

So would we advertise i.I for accessing the internal tuple for destructuring, or do we view that as an implementation detail and introduce the Tuple(::CartesianIndex) method as a means for accessing it?

@timholy
Copy link
Sponsor Member

timholy commented Sep 18, 2017

I like the Tuple version better.

@martinholters martinholters changed the title Make CartesianIndex iterable Add Tuple(::CartesianIndex) Sep 19, 2017
@martinholters
Copy link
Member Author

Ok, changed the PR to only add the Tuple method instead of adding iteration capabilities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
collections Data structures holding multiple items, e.g. sets
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants