Skip to content

Commit d61c7e4

Browse files
committed
Merge pull request #10704 from mbauman/mb/eachindex
Make eachindex more efficient for linear arrays
2 parents 5d36ee4 + adde889 commit d61c7e4

File tree

7 files changed

+79
-81
lines changed

7 files changed

+79
-81
lines changed

base/abstractarray.jl

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,24 @@ end
337337

338338
zero{T}(x::AbstractArray{T}) = fill!(similar(x), zero(T))
339339

340-
## iteration support for arrays as ranges ##
340+
## iteration support for arrays by iterating over `eachindex` in the array ##
341+
# Allows fast iteration by default for both LinearFast and LinearSlow arrays
342+
343+
# While the definitions for LinearFast are all simple enough to inline on their
344+
# own, LinearSlow's CartesianRange is more complicated and requires explicit
345+
# inlining. The real @inline macro is not available this early in the bootstrap,
346+
# so this internal macro splices the meta Expr directly into the function body.
347+
macro _inline_meta()
348+
Expr(:meta, :inline)
349+
end
350+
start(A::AbstractArray) = (@_inline_meta(); itr = eachindex(A); (itr, start(itr)))
351+
next(A::AbstractArray,i) = (@_inline_meta(); (idx, s) = next(i[1], i[2]); (A[idx], (i[1], s)))
352+
done(A::AbstractArray,i) = done(i[1], i[2])
353+
354+
# eachindex iterates over all indices. LinearSlow definitions are later.
355+
eachindex(A::AbstractArray) = (@_inline_meta; eachindex(linearindexing(A), A))
356+
eachindex(::LinearFast, A::AbstractArray) = 1:length(A)
341357

342-
start(A::AbstractArray) = _start(A,linearindexing(A))
343-
_start(::AbstractArray,::LinearFast) = 1
344-
next(a::AbstractArray,i) = (a[i],i+1)
345-
done(a::AbstractArray,i) = (i > length(a))
346358
isempty(a::AbstractArray) = (length(a) == 0)
347359

348360
## Conversions ##

base/array.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ end
297297

298298
collect(itr) = collect(eltype(itr), itr)
299299

300+
## Iteration ##
301+
start(A::Array) = 1
302+
next(a::Array,i) = (a[i],i+1)
303+
done(a::Array,i) = (i > length(a))
304+
300305
## Indexing: getindex ##
301306

302307
getindex(a::Array) = arrayref(a,1)

base/dict.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ function skip_deleted(h::Dict, i)
713713
end
714714

715715
start(t::Dict) = skip_deleted(t, 1)
716-
done(t::Dict, i) = done(t.vals, i)
716+
done(t::Dict, i) = i > length(t.vals)
717717
next(t::Dict, i) = ((t.keys[i],t.vals[i]), skip_deleted(t,i+1))
718718

719719
isempty(t::Dict) = (t.count == 0)

base/multidimensional.jl

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
### Multidimensional iterators
22
module IteratorsMD
33

4-
import Base: eltype, length, start, _start, done, next, last, getindex, setindex!, linearindexing, min, max
4+
import Base: eltype, length, start, done, next, last, getindex, setindex!, linearindexing, min, max, eachindex
55
import Base: simd_outer_range, simd_inner_length, simd_index
66
import Base: @nref, @ncall, @nif, @nexprs, LinearFast, LinearSlow, to_index
77

8-
export CartesianIndex, CartesianRange, eachindex
8+
export CartesianIndex, CartesianRange
99

1010
# Traits for linear indexing
1111
linearindexing(::BitArray) = LinearFast()
@@ -110,60 +110,37 @@ stagedfunction CartesianRange{N}(I::CartesianIndex{N})
110110
end
111111
CartesianRange{N}(sz::NTuple{N,Int}) = CartesianRange(CartesianIndex(sz))
112112

113-
stagedfunction eachindex{T,N}(A::AbstractArray{T,N})
113+
stagedfunction eachindex{T,N}(::LinearSlow, A::AbstractArray{T,N})
114114
startargs = fill(1, N)
115115
stopargs = [:(size(A,$i)) for i=1:N]
116-
:(CartesianRange(CartesianIndex{$N}($(startargs...)), CartesianIndex{$N}($(stopargs...))))
116+
meta = Expr(:meta, :inline)
117+
:($meta; CartesianRange(CartesianIndex{$N}($(startargs...)), CartesianIndex{$N}($(stopargs...))))
117118
end
118119

119120
eltype{I}(::Type{CartesianRange{I}}) = I
120121
eltype{I}(::CartesianRange{I}) = I
121122

122-
stagedfunction start{I}(iter::CartesianRange{I})
123-
N=length(I)
124-
finishedex = Expr(:(||), [:(iter.stop[$i] < iter.start[$i]) for i = 1:N]...)
125-
:(return $finishedex, iter.start)
126-
end
127-
128-
stagedfunction _start{T,N}(A::AbstractArray{T,N}, ::LinearSlow)
129-
args = fill(1, N)
130-
:(return isempty(A), CartesianIndex{$N}($(args...)))
131-
end
132-
133-
# Prevent an ambiguity warning
134-
next(R::StepRange, state::(Bool, CartesianIndex{1})) = (index=state[2]; return R[index], (index[1]==length(R), CartesianIndex{1}(index[1]+1)))
135-
next{T}(R::UnitRange{T}, state::(Bool, CartesianIndex{1})) = (index=state[2]; return R[index], (index[1]==length(R), CartesianIndex{1}(index[1]+1)))
136-
done(R::StepRange, state::(Bool, CartesianIndex{1})) = state[1]
137-
done(R::UnitRange, state::(Bool, CartesianIndex{1})) = state[1]
138-
139-
stagedfunction next{T,N}(A::AbstractArray{T,N}, state::(Bool, CartesianIndex{N}))
140-
I = state[2]
141-
finishedex = (N==0 ? true : :(newindex[$N] > size(A, $N)))
123+
start(iter::CartesianRange) = iter.start
124+
stagedfunction next{I<:CartesianIndex}(iter::CartesianRange{I}, state)
125+
N = length(I)
142126
meta = Expr(:meta, :inline)
143127
quote
144128
$meta
145-
index=state[2]
146-
@inbounds v = A[index]
147-
newindex=@nif $N d->(index[d] < size(A, d)) d->@ncall($N, $I, k->(k>d ? index[k] : k==d ? index[k]+1 : 1))
148-
finished=$finishedex
149-
v, (finished,newindex)
129+
index=state
130+
@nif $N d->(index[d] < iter.stop[d]) d->(@nexprs($N, k->(ind_k = ifelse(k>=d, index[k] + (k==d), iter.start[k]))))
131+
newindex = @ncall $N $I ind
132+
index, newindex
150133
end
151134
end
152-
stagedfunction next{I<:CartesianIndex}(iter::CartesianRange{I}, state::(Bool, I))
135+
stagedfunction done{I<:CartesianIndex}(iter::CartesianRange{I}, state)
153136
N = length(I)
154-
finishedex = (N==0 ? true : :(newindex[$N] > iter.stop[$N]))
155-
meta = Expr(:meta, :inline)
156-
quote
157-
$meta
158-
index=state[2]
159-
newindex=@nif $N d->(index[d] < iter.stop[d]) d->@ncall($N, $I, k->(k>d ? index[k] : k==d ? index[k]+1 : iter.start[k]))
160-
finished=$finishedex
161-
index, (finished,newindex)
162-
end
137+
:(state[$N] > iter.stop[$N])
163138
end
164139

165-
done{T,N}(A::AbstractArray{T,N}, state::(Bool, CartesianIndex{N})) = state[1]
166-
done{I<:CartesianIndex}(iter::CartesianRange{I}, state::(Bool, I)) = state[1]
140+
# 0-d cartesian ranges are special-cased to iterate once and only once
141+
start{I<:CartesianIndex{0}}(iter::CartesianRange{I}) = false
142+
next{I<:CartesianIndex{0}}(iter::CartesianRange{I}, state) = iter.start, true
143+
done{I<:CartesianIndex{0}}(iter::CartesianRange{I}, state) = state
167144

168145
stagedfunction length{I<:CartesianIndex}(iter::CartesianRange{I})
169146
N = length(I)
@@ -502,7 +479,7 @@ function copy!{T,N}(dest::AbstractArray{T,N}, src::AbstractArray{T,N})
502479
break
503480
end
504481
end
505-
if samesize
482+
if samesize && linearindexing(dest) == linearindexing(src)
506483
for I in eachindex(dest)
507484
@inbounds dest[I] = src[I]
508485
end

base/reducedim.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ function _mapreducedim!{T,N}(f, op, R::AbstractArray, A::AbstractArray{T,N})
209209
end
210210
else
211211
sizeR = CartesianIndex{N}(size(R))
212-
@inbounds @simd for IA in eachindex(A)
212+
@inbounds @simd for IA in CartesianRange(size(A))
213213
IR = min(IA, sizeR)
214214
R[IR] = op(R[IR], f(A[IA]))
215215
end
@@ -306,7 +306,7 @@ function findminmax!{T,N}(f, Rval, Rind, A::AbstractArray{T,N})
306306
end
307307
else
308308
sizeR = CartesianIndex(size(Rval))
309-
@inbounds for IA in eachindex(A)
309+
@inbounds for IA in CartesianRange(size(A))
310310
IR = min(sizeR, IA)
311311
k += 1
312312
tmpAv = A[IA]

doc/stdlib/arrays.rst

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,31 @@ Basic functions
3333

3434
.. function:: eachindex(A)
3535

36-
Creates an iterable object for visiting each multi-dimensional index of the AbstractArray ``A``. Example for a 2-d array::
36+
Creates an iterable object for visiting each index of an AbstractArray ``A`` in an efficient manner. For array types that have opted into fast linear indexing (like ``Array``), this is simply the range ``1:length(A)``. For other array types, this returns a specialized Cartesian range to efficiently index into the array with indices specified for every dimension. Example for a sparse 2-d array::
3737

38-
julia> A = rand(2,3)
39-
2x3 Array{Float64,2}:
40-
0.960084 0.629326 0.625155
41-
0.432588 0.955903 0.991614
38+
julia> A = sprand(2, 3, 0.5)
39+
2x3 sparse matrix with 4 Float64 entries:
40+
[1, 1] = 0.598888
41+
[1, 2] = 0.0230247
42+
[1, 3] = 0.486499
43+
[2, 3] = 0.809041
4244

4345
julia> for iter in eachindex(A)
44-
@show iter.I_1, iter.I_2
45-
@show A[iter]
46-
end
46+
@show iter.I_1, iter.I_2
47+
@show A[iter]
48+
end
4749
(iter.I_1,iter.I_2) = (1,1)
48-
A[iter] = 0.9600836263003063
50+
A[iter] = 0.5988881393454597
4951
(iter.I_1,iter.I_2) = (2,1)
50-
A[iter] = 0.4325878255452178
52+
A[iter] = 0.0
5153
(iter.I_1,iter.I_2) = (1,2)
52-
A[iter] = 0.6293256402775211
54+
A[iter] = 0.02302469881746183
5355
(iter.I_1,iter.I_2) = (2,2)
54-
A[iter] = 0.9559027084099654
56+
A[iter] = 0.0
5557
(iter.I_1,iter.I_2) = (1,3)
56-
A[iter] = 0.6251548453735303
58+
A[iter] = 0.4864987874354343
5759
(iter.I_1,iter.I_2) = (2,3)
58-
A[iter] = 0.9916142534546522
60+
A[iter] = 0.8090413606455655
5961

6062
.. function:: countnz(A)
6163

test/arrayops.jl

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,7 @@ I2 = CartesianIndex((-1,5,2))
993993

994994
@test length(I1) == 3
995995

996-
a = zeros(2,3)
996+
a = spzeros(2,3)
997997
@test CartesianRange(size(a)) == eachindex(a)
998998
a[CartesianIndex{2}(2,3)] = 5
999999
@test a[2,3] == 5
@@ -1015,22 +1015,24 @@ indexes = collect(R)
10151015
@test length(R) == 12
10161016

10171017
r = 2:3
1018-
state = start(eachindex(r))
1019-
@test !done(r, state)
1020-
_, state = next(r, state)
1021-
@test !done(r, state)
1022-
val, state = next(r, state)
1023-
@test done(r, state)
1024-
@test val == 3
1025-
r = 2:3:8
1026-
state = start(eachindex(r))
1027-
@test !done(r, state)
1028-
_, state = next(r, state)
1029-
_, state = next(r, state)
1030-
@test !done(r, state)
1031-
val, state = next(r, state)
1032-
@test val == 8
1033-
@test done(r, state)
1018+
itr = eachindex(r)
1019+
state = start(itr)
1020+
@test !done(itr, state)
1021+
_, state = next(itr, state)
1022+
@test !done(itr, state)
1023+
val, state = next(itr, state)
1024+
@test done(itr, state)
1025+
@test r[val] == 3
1026+
r = sparse(collect(2:3:8))
1027+
itr = eachindex(r)
1028+
state = start(itr)
1029+
@test !done(itr, state)
1030+
_, state = next(itr, state)
1031+
_, state = next(itr, state)
1032+
@test !done(itr, state)
1033+
val, state = next(itr, state)
1034+
@test r[val] == 8
1035+
@test done(itr, state)
10341036

10351037

10361038
#rotates

0 commit comments

Comments
 (0)