Skip to content

Commit 8472d54

Browse files
committed
Merge pull request #8501 from JuliaLang/teh/subarray
New array views based on stagedfunctions
2 parents cf1f26e + b5705d3 commit 8472d54

13 files changed

+1268
-497
lines changed

base/abstractarray.jl

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ immutable LinearSlow <: LinearIndexing end
6767
linearindexing(::AbstractArray) = LinearSlow()
6868
linearindexing(::Array) = LinearFast()
6969
linearindexing(::Range) = LinearFast()
70+
linearindexing{A<:AbstractArray}(::Type{A}) = LinearSlow()
71+
linearindexing{A<:Array}(::Type{A}) = LinearFast()
72+
linearindexing{A<:Range}(::Type{A}) = LinearFast()
7073

7174
## Bounds checking ##
7275
checkbounds(sz::Int, i::Int) = 1 <= i <= sz || throw(BoundsError())

base/array.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ typealias DenseVector{T} DenseArray{T,1}
88
typealias DenseMatrix{T} DenseArray{T,2}
99
typealias DenseVecOrMat{T} Union(DenseVector{T}, DenseMatrix{T})
1010

11-
typealias StridedArray{T,N,A<:DenseArray} Union(DenseArray{T,N}, SubArray{T,N,A})
12-
typealias StridedVector{T,A<:DenseArray} Union(DenseArray{T,1}, SubArray{T,1,A})
13-
typealias StridedMatrix{T,A<:DenseArray} Union(DenseArray{T,2}, SubArray{T,2,A})
11+
typealias StridedArray{T,N,A<:DenseArray,I<:(RangeIndex...)} Union(DenseArray{T,N}, SubArray{T,N,A,I})
12+
typealias StridedVector{T,A<:DenseArray,I<:(RangeIndex...)} Union(DenseArray{T,1}, SubArray{T,1,A,I})
13+
typealias StridedMatrix{T,A<:DenseArray,I<:(RangeIndex...)} Union(DenseArray{T,2}, SubArray{T,2,A,I})
1414
typealias StridedVecOrMat{T} Union(StridedVector{T}, StridedMatrix{T})
1515

1616
## Basic functions ##

base/darray.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ function getindex_tuple{T}(d::DArray{T}, I::(Int...))
243243
end
244244

245245
getindex(d::DArray) = d[1]
246-
getindex(d::DArray, I::Union(Int,UnitRange{Int})...) = sub(d,I)
246+
getindex(d::DArray, I::Union(Int,UnitRange{Int})...) = sub(d,I...)
247247

248248
copy(d::SubOrDArray) = d
249249

base/multidimensional.jl

+100-30
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export eachindex
88

99
# Traits for linear indexing
1010
linearindexing(::BitArray) = LinearFast()
11+
linearindexing{A<:BitArray}(::Type{A}) = LinearFast()
1112

1213
# Iterator/state
1314
abstract CartesianIndex{N} # the state for all multidimensional iterators
@@ -134,11 +135,7 @@ using .IteratorsMD
134135
nothing
135136
end
136137

137-
unsafe_getindex(v::Real, ind::Int) = v
138-
unsafe_getindex(v::Range, ind::Int) = first(v) + (ind-1)*step(v)
139138
unsafe_getindex(v::BitArray, ind::Int) = Base.unsafe_bitgetindex(v.chunks, ind)
140-
unsafe_getindex(v::AbstractArray, ind::Int) = v[ind]
141-
unsafe_getindex(v, ind::Real) = unsafe_getindex(v, to_index(ind))
142139

143140
unsafe_setindex!{T}(v::AbstractArray{T}, x::T, ind::Int) = (v[ind] = x; v)
144141
unsafe_setindex!(v::BitArray, x::Bool, ind::Int) = (Base.unsafe_bitsetindex!(v.chunks, x, ind); v)
@@ -226,42 +223,115 @@ end
226223

227224
### subarray.jl
228225

229-
# Here we want to skip creating the dict-based cached version,
230-
# so use the ngenerate function
231-
function gen_getindex_body(N::Int)
226+
function gen_setindex_body(N::Int)
232227
quote
233-
strd_1 = 1
234-
@nexprs $N d->(@inbounds strd_{d+1} = strd_d*s.dims[d])
235-
ind -= 1
236-
indp = s.first_index
237-
@nexprs $N d->begin
238-
i = div(ind, strd_{$N-d+1})
239-
@inbounds indp += i*s.strides[$N-d+1]
240-
ind -= i*strd_{$N-d+1}
228+
Base.Cartesian.@nexprs $N d->(J_d = J[d])
229+
Base.Cartesian.@ncall $N checkbounds V J
230+
Base.Cartesian.@nexprs $N d->(I_d = Base.to_index(J_d))
231+
if !isa(x, AbstractArray)
232+
Base.Cartesian.@nloops $N i d->(1:length(I_d)) d->(@inbounds j_d = Base.unsafe_getindex(I_d, i_d)) begin
233+
@inbounds (Base.Cartesian.@nref $N V j) = x
234+
end
235+
else
236+
X = x
237+
Base.Cartesian.@ncall $N Base.setindex_shape_check X I
238+
k = 1
239+
Base.Cartesian.@nloops $N i d->(1:length(I_d)) d->(@inbounds j_d = Base.unsafe_getindex(I_d, i_d)) begin
240+
@inbounds (Base.Cartesian.@nref $N V j) = X[k]
241+
k += 1
242+
end
241243
end
242-
s.parent[indp]
244+
V
243245
end
244246
end
245247

246-
eval(ngenerate(:N, nothing, :(getindex{T}(s::SubArray{T,N}, ind::Integer)), gen_getindex_body, 2:5, false))
247-
248+
## SubArray index merging
249+
# A view created like V = A[2:3:8, 5:2:17] can later be indexed as V[2:7],
250+
# creating a new 1d view.
251+
# In such cases we have to collapse the 2d space spanned by the ranges.
252+
#
253+
# API:
254+
# merge_indexes(V, indexes::NTuple, dims::Dims, linindex)
255+
# where dims encodes the trailing sizes of the parent array,
256+
# indexes encodes the view's trailing indexes into the parent array,
257+
# and linindex encodes the subset of these elements that we'll select.
258+
#
259+
# The generic algorithm makes use of div to convert elements
260+
# of linindex into a cartesian index into indexes, looks up
261+
# the corresponding cartesian index into the parent, and then uses
262+
# dims to convert back to a linear index into the parent array.
263+
#
264+
# However, a common case is linindex::UnitRange.
265+
# Since div is slow and in(j::Int, linindex::UnitRange) is fast,
266+
# it can be much faster to generate all possibilities and
267+
# then test whether the corresponding linear index is in linindex.
268+
# One exception occurs when only a small subset of the total
269+
# is desired, in which case we fall back to the div-based algorithm.
270+
stagedfunction merge_indexes(V, indexes::NTuple, dims::Dims, linindex::UnitRange{Int})
271+
N = length(indexes)
272+
N > 0 || error("Cannot merge empty indexes")
273+
quote
274+
n = length(linindex)
275+
Base.Cartesian.@nexprs $N d->(I_d = indexes[d])
276+
L = 1
277+
dimoffset = ndims(V.parent) - length(dims)
278+
Base.Cartesian.@nexprs $N d->(L *= dimsize(V.parent, d+dimoffset, I_d))
279+
if n < 0.1L # this has not been carefully tuned
280+
return merge_indexes_div(V, indexes, dims, linindex)
281+
end
282+
Pstride_1 = 1 # parent strides
283+
Base.Cartesian.@nexprs $(N-1) d->(Pstride_{d+1} = Pstride_d*dims[d])
284+
Istride_1 = 1 # indexes strides
285+
Base.Cartesian.@nexprs $(N-1) d->(Istride_{d+1} = Istride_d*dimsize(V, d+dimoffset, I_d))
286+
Base.Cartesian.@nexprs $N d->(counter_d = 1) # counter_0 is a linear index into indexes
287+
Base.Cartesian.@nexprs $N d->(offset_d = 1) # offset_0 is a linear index into parent
288+
k = 0
289+
index = Array(Int, n)
290+
Base.Cartesian.@nloops $N i d->(1:dimsize(V, d+dimoffset, I_d)) d->(offset_{d-1} = offset_d + (I_d[i_d]-1)*Pstride_d; counter_{d-1} = counter_d + (i_d-1)*Istride_d) begin
291+
if in(counter_0, linindex)
292+
index[k+=1] = offset_0
293+
end
294+
end
295+
index
296+
end
297+
end
298+
merge_indexes(V, indexes::NTuple, dims::Dims, linindex) = merge_indexes_div(V, indexes, dims, linindex)
248299

249-
function gen_setindex!_body(N::Int)
300+
# This could be written as a regular function, but performance
301+
# will be better using Cartesian macros to avoid the heap and
302+
# an extra loop.
303+
stagedfunction merge_indexes_div(V, indexes::NTuple, dims::Dims, linindex)
304+
N = length(indexes)
305+
N > 0 || error("Cannot merge empty indexes")
306+
Istride_N = symbol("Istride_$N")
250307
quote
251-
strd_1 = 1
252-
@nexprs $N d->(@inbounds strd_{d+1} = strd_d*s.dims[d])
253-
ind -= 1
254-
indp = s.first_index
255-
@nexprs $N d->begin
256-
i = div(ind, strd_{$N-d+1})
257-
@inbounds indp += i*s.strides[$N-d+1]
258-
ind -= i*strd_{$N-d+1}
308+
Base.Cartesian.@nexprs $N d->(I_d = indexes[d])
309+
Pstride_1 = 1 # parent strides
310+
Base.Cartesian.@nexprs $(N-1) d->(Pstride_{d+1} = Pstride_d*dims[d])
311+
Istride_1 = 1 # indexes strides
312+
dimoffset = ndims(V.parent) - length(dims)
313+
Base.Cartesian.@nexprs $(N-1) d->(Istride_{d+1} = Istride_d*dimsize(V.parent, d+dimoffset, I_d))
314+
n = length(linindex)
315+
L = $(Istride_N) * dimsize(V.parent, $N+dimoffset, indexes[end])
316+
index = Array(Int, n)
317+
for i = 1:n
318+
k = linindex[i] # k is the indexes-centered linear index
319+
1 <= k <= L || throw(BoundsError())
320+
k -= 1
321+
j = 0 # j will be the new parent-centered linear index
322+
Base.Cartesian.@nexprs $N d->(d < $N ?
323+
begin
324+
c, k = divrem(k, Istride_{$N-d+1})
325+
j += (Base.unsafe_getindex(I_{$N-d+1}, c+1)-1)*Pstride_{$N-d+1}
326+
end : begin
327+
j += Base.unsafe_getindex(I_1, k+1)
328+
end)
329+
index[i] = j
259330
end
260-
s.parent[indp] = v
331+
index
261332
end
262333
end
263334

264-
eval(ngenerate(:N, nothing, :(setindex!{T}(s::SubArray{T,N}, v, ind::Integer)), gen_setindex!_body, 2:5, false))
265335

266336
cumsum(A::AbstractArray, axis::Integer=1) = cumsum!(similar(A, Base._cumsum_type(A)), A, axis)
267337
cumprod(A::AbstractArray, axis::Integer=1) = cumprod!(similar(A), A, axis)
@@ -622,7 +692,7 @@ for (V, PT, BT) in [((:N,), BitArray, BitArray), ((:T,:N), Array, StridedArray)]
622692
offset = 1 - sum(@ntuple N d->strides_{d+1})
623693

624694
if isa(B, SubArray)
625-
offset += B.first_index - 1
695+
offset += first_index(B) - 1
626696
B = B.parent
627697
end
628698

0 commit comments

Comments
 (0)