I am fooling around with 1.30-rc1 and would like to understand how to make a call on mapslices threaded.
Since mapslices can emulate a loop over an array dimension, it would nice to make that “loop” Threads.@threads. Does anyone have a hint?
I am fooling around with 1.30-rc1 and would like to understand how to make a call on mapslices threaded.
Since mapslices can emulate a loop over an array dimension, it would nice to make that “loop” Threads.@threads. Does anyone have a hint?
Writing a loop over eachcol(x)
is usually faster than mapslices
even before you add threading. Or you can use my SliceMap, which has tmapcols
. Or use JuliennedArrays for more complicated slicing.
My use case us that I want to write code that can take an AbstractArray
as input and then do operations along one dimension. In some calls the array will be a Matrix
, but in other cases there will be more dimensions.
This rules out eachcol
and probably SliceMap
, or? Not even eachslice
seems to work well since it only allows a single dimension in dims
.
If I understand right, then yes, eachslice
allows only one “outer” dimension, meaning that your function will get N-1
-dimensional arrays when acting on an N
-array. Slightly confusingly mapslices
takes the “inner” dimensions as dims
instead, as do Slices
and slicemap
. But these should be more general:
julia> map(ndims, eachslice(ones(1,2,3), dims=3))
3-element Array{Int64,1}:
2
2
2
julia> mapslices(ndims, ones(1,2,3), dims=(1,2))
1×1×3 Array{Int64,3}:
[:, :, 1] =
2
[:, :, 2] =
2
[:, :, 3] =
2
julia> using JuliennedArrays
julia> map(ndims, Slices(ones(1,2,3), 1,2))
3-element Array{Int64,1}:
2
2
2
julia> using SliceMap
julia> slicemap(ndims, ones(1,2,3), dims=(1,2))
3-element Array{Int64,1}:
2
2
2
Here of course the function gives a scalar; had it made an array then the map
examples would be arrays of arrays, needing reduce(hcat,...
or Align
etc, which is built into mapslices
/ slicemap
.
Here is my solution. Hope it helps.
import Base.Threads
import Base: Slice, concatenate_setindex!
"""
par_mapslices(f, A::AbstractArray{<:Real,N}, args...; dims=N, kw...)
# Arguments
- `dims`: the dimension apply f
@seealso `mapslices`
# Example
```julia
A = rand(10, 10, 30, 4)
par_mapslices(mean, A)
```
"""
function par_mapslices(f, A::AbstractArray{<:Real,N}, args...; dims=N, kw...) where {N}
idx1 = ntuple(d -> d in dims ? (:) : firstindex(A, d), ndims(A))
Aslice = A[idx1...]
r1 = f(Aslice)
_dims = size(A) |> collect
_dims[dims] = length(r1)
dim_mask = ntuple(d -> d in dims, ndims(A))
itershape = ntuple(d -> d in dims ? Base.OneTo(1) : axes(A, d), ndims(A))
indices = CartesianIndices(itershape)
R = zeros(eltype(r1), _dims...)
slice_A = Slice.(axes(A))
slice_R = Slice.(axes(R))
@inbounds Threads.@threads for I in indices
idx = ifelse.(dim_mask, slice_A, Tuple(I))
ridx = ifelse.(dim_mask, slice_R, Tuple(I))
# _unsafe_getindex!(Aslice, A, idx...)
Aslice = @view A[idx...]
r = f(Aslice, args...; kw...)
concatenate_setindex!(R, r, ridx...)
end
R |> squeeze
end
```