-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
selectslice, A function similar to selectdim, returning a vector like slice along a dimension at specified indices. #32754
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
|
I am still unsure whether this is an improvement. Maybe it would help if you could describe your/a usecase where only the function form can be used? |
|
Interesting! Thanks for the contribution, and welcome! Like @fkastner I'd love to see some concrete use-cases here and how they compare to plain old indexing. I'm not worried about the performance — if we decide we want this we can make it fast by using the same tricks |
|
I am trying to write an N dimensional form of one of my utility functions, shown here in the 3d case, which multiplies every vector like strip of an Array with a matrix like object, along a certain dimension, and stores the result in a new array. I also have a version where the multiplying matrix is different for every individual strip. I could be wrong, but I don't think that there is any other way of doing this at the moment, as there is a need to create a view in to two different arrays, so |
Co-Authored-By: Felix Kastner <kastner.felix@gmail.com>
|
An, nice, thanks for that example. I think the way I'd write that is with Now, the annoying thing is that the computation of If you've not seen this before, you might find this blog post interesting — it uses the above approach and gives many more details. https://julialang.org/blog/2016/02/iteration That's not to say that this isn't a handy utility, but I think the hard part is constructing the iteration space. You still need to do that for |
|
Thanks! I have seen |
|
For a single dimension I think this is what @mbauman meant: For me this is twice as fast as the current code in this PR. I'm currently trying to come up with a multi-dimensional version of this, that accepts a tuple of dimensions along which the slice should be taken, but a) it's slower and b) it doesn't yet work for some edge cases. This could also be the basis for a function that "hides" some dimension of the array, i.e. for the above example it would return an AbstractArray of size 3x5 where each element is a 4x6 view. |
|
I think my implementation works now. It uses a generated function to compute the indices. @generated function slice_inds(dim::T, inds::NTuple{M,T}) where {M,T<:Integer}
return :((inds[1:dim-1]..., Colon(), inds[dim:end]...))#::NTuple{$(M+1),Union{Colon,$T}})
end
@generated function slice_inds(dim::NTuple{N,T}, inds::NTuple{M,T}) where {N,M,T<:Integer,S<:Integer}
N == 0 && return :(inds)#::NTuple{$(N+M),Union{Colon,$T}})
args = Vector{Expr}(undef,2*N+1)
args[1] = :(inds[1:dim[1]-1]...)
for i=1:N-1
args[2*i] = :(Colon())
args[2*i+1] = :(inds[dim[$i]-$(i-1):dim[$(i+1)]-$(i+1)]...)
end
args[2*N] = :(Colon())
args[2*N+1] = :(inds[dim[$(N)]-$(N-1):end]...)
ex = :(tuple($(args...)))#::NTuple{$(N+M),Union{Colon,$T}})
end
selectslice_nd(A::AbstractArray, inds...; dims) = _selectslice_nd(A, dims, slice_inds(dims,inds))
@noinline function _selectslice_nd(A, dims, inds)
all(dims .>= 1) || throw(ArgumentError("dimensions must be ≥ 1, got $dims"))
all(dims .<= ndims(A)) || throw(ArgumentError("dimensions must be ≤ ndims(A), got $dims"))
length(inds) == ndims(A) || throw(ArgumentError("there must be ndims(A) indices, got $(length(inds))"))
view(A, inds...)
endExample: julia> d = [(i,j,k,l) for i in 1:3, j = 1:4, k = 1:5, l=1:6]
3×4×5×6 Array{NTuple{4,Int64},4}:
[...]
julia> selectslice(d,3,2,1,4) == selectslice_nd(d,2,1,4, dims=3) == d[2,1,:,4]
true
julia> @btime selectslice($d,3,2,1,4); @btime selectslice_nd($d,2,1,4, dims=3);
751.151 ns (8 allocations: 400 bytes)
976.385 ns (7 allocations: 368 bytes)
julia> selectslice_nd(d,1,4, dims=(1,3)) == d[:,1,:,4]
true
julia> @btime selectslice_nd($d,1,4, dims=(1,3));
921.706 ns (7 allocations: 352 bytes) |
A type instability means that Julia isn't able to predict what type something is going to be — and that means that it has to use pessimistic storage for it since it doesn't know how big it'll be and it needs to use dynamic dispatch to look up what method to call. Where it gets really bad is that Julia also knows nothing (ahead of time) about what its downstream functions will return — thus needing lots of pessimistic storage (boxes) and lots of subsequent dynamic dispatch... possibly on every single iteration of your loops! Once you're inside a new function, Julia has now done the work to figure out what the types were and is able to generate super-fast efficient code. So we pay a small cost for the lookup once, and then go into that |
|
This should accept a |
vtjnash
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.
@JeffBezanson is this what you meant?
| @noinline function selectslice(A::AbstractArray{T,N}, dim, inds...) where {T,N} | ||
| dim >= 1 || throw(ArgumentError("dimension must be ≥ 1, got $dim")) |
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.
| @noinline function selectslice(A::AbstractArray{T,N}, dim, inds...) where {T,N} | |
| dim >= 1 || throw(ArgumentError("dimension must be ≥ 1, got $dim")) | |
| @noinline function selectslice(A::AbstractArray{T,N}, inds...; dims) where {T, N} | |
| length(dims) == 1 || throw(ArgumentError("only single dimensions are supported")) | |
| dim = first(dims) | |
| dim >= 1 || throw(ArgumentError("dimension must be ≥ 1, got $dim")) |
| end | ||
|
|
||
| """ | ||
| selectslice(A::AbstractArray, dim::Integer, inds...) |
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.
| selectslice(A::AbstractArray, dim::Integer, inds...) | |
| selectslice(A::AbstractArray, inds...; dims) |
| """ | ||
| selectslice(A::AbstractArray, dim::Integer, inds...) | ||
| Return a view in to the vector shaped slice of `A` along the dimension `dim` with all the other indices set to `inds` in order. |
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.
| Return a view in to the vector shaped slice of `A` along the dimension `dim` with all the other indices set to `inds` in order. | |
| Return a view into the vector shaped slice of `A` along the dimensions `dims` | |
| with all the other indices set to `inds` in order. (Only a single dimension in | |
| `dims` is currently supported.) |
|
I think with the introduction of multidimensional Using the examples from above we now have: julia> A = [1 2 3 4; 5 6 7 8]
2×4 Matrix{Int64}:
1 2 3 4
5 6 7 8
julia> selectslice(A, 2, 2) == eachslice(A, dims=1)[2]
trueas well as julia> d = [(i,j,k,l) for i in 1:3, j = 1:4, k = 1:5, l=1:6]
3×4×5×6 Array{NTuple{4,Int64},4}:
[...]
julia> selectslice_nd(d, 1, 4, dims=(1,3)) == eachslice(d, dims=(2,4))[1,4]
trueAdditionally |
|
Well, thanks for proposing this, @xtalax! I'm sorry your PR here didn't land, but I'm glad we now have the functionality you were after (albeit backwards). It's perhaps also worth noting that the tuple slicing syntaxes I had written above are also now type stable when |
From the code:
selectslice(A::AbstractArray, dim::Integer, inds...)Return a view in to the vector shaped slice of
Aalong the dimensiondimwith all the other indicies set toindsin order.Equivalent to
view(A, inds[1], inds[2], ..., :, inds[dim], inds[dim+1], ...)#Examples
#Current Benchmark
Credit to Felix Kastner for helping to improve the performance
Looking for advice on how to improve performance, particularly avoiding
[inds...]and other uses of splat.