Skip to content

Commit 5ee74cf

Browse files
authored
Faster iteration for an IdOffsetRange (#215)
* specialize for Base.OneTo * rebase on master * use helper functions instead of broadcasting
1 parent f5cd050 commit 5ee74cf

File tree

3 files changed

+52
-9
lines changed

3 files changed

+52
-9
lines changed

src/axes.jl

+15-7
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange =
167167
@inline Base.axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset)
168168
@inline Base.unsafe_indices(r::IdOffsetRange) = (Base.axes1(r),)
169169
@inline Base.length(r::IdOffsetRange) = length(r.parent)
170+
@inline Base.isempty(r::IdOffsetRange) = isempty(r.parent)
170171
Base.reduced_index(i::IdOffsetRange) = typeof(i)(first(i):first(i))
171172
# Workaround for #92 on Julia < 1.4
172173
Base.reduced_index(i::IdentityUnitRange{<:IdOffsetRange}) = typeof(i)(first(i):first(i))
@@ -178,16 +179,23 @@ for f in [:first, :last]
178179
@eval @inline Base.$f(r::IdOffsetRange) = eltype(r)($f(r.parent) + r.offset)
179180
end
180181

181-
@inline function Base.iterate(r::IdOffsetRange)
182-
ret = iterate(r.parent)
183-
ret === nothing && return nothing
184-
return (eltype(r)(ret[1] + r.offset), ret[2])
185-
end
186-
@inline function Base.iterate(r::IdOffsetRange, i)
187-
ret = iterate(r.parent, i)
182+
# Iteration for an IdOffsetRange
183+
@inline Base.iterate(r::IdOffsetRange, i...) = _iterate(r, i...)
184+
# In general we iterate over the parent term by term and add the offset.
185+
# This might have some performance degradation when coupled with bounds-checking
186+
# See https://github.com/JuliaArrays/OffsetArrays.jl/issues/214
187+
@inline function _iterate(r::IdOffsetRange, i...)
188+
ret = iterate(r.parent, i...)
188189
ret === nothing && return nothing
189190
return (eltype(r)(ret[1] + r.offset), ret[2])
190191
end
192+
# Base.OneTo(n) is known to be exactly equivalent to the range 1:n,
193+
# and has no specialized iteration defined for it,
194+
# so we may add the offset to the range directly and iterate over the result
195+
# This gets around the performance issue described in issue #214
196+
# We use the helper function _addoffset to evaluate the range instead of broadcasting
197+
# just in case this makes it easy for the compiler.
198+
@inline _iterate(r::IdOffsetRange{<:Integer, <:Base.OneTo}, i...) = iterate(_addoffset(r.parent, r.offset), i...)
191199

192200
@inline function Base.getindex(r::IdOffsetRange, i::Integer)
193201
i isa Bool && throw(ArgumentError("invalid index: $i of type Bool"))

src/utils.jl

+6-2
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ end
9292
# These functions are equivalent to the broadcasted operation r .- of
9393
# However these ensure that the result is an AbstractRange even if a specific
9494
# broadcasting behavior is not defined for a custom type
95-
_subtractoffset(r::AbstractUnitRange, of) = UnitRange(first(r) - of, last(r) - of)
96-
_subtractoffset(r::AbstractRange, of) = range(first(r) - of, stop = last(r) - of, step = step(r))
95+
@inline _subtractoffset(r::AbstractUnitRange, of) = UnitRange(first(r) - of, last(r) - of)
96+
@inline _subtractoffset(r::AbstractRange, of) = range(first(r) - of, stop = last(r) - of, step = step(r))
97+
98+
# similar to _subtractoffset, except these evaluate r .+ of
99+
@inline _addoffset(r::AbstractUnitRange, of) = UnitRange(first(r) + of, last(r) + of)
100+
@inline _addoffset(r::AbstractRange, of) = range(first(r) + of, stop = last(r) + of, step = step(r))
97101

98102
if VERSION <= v"1.7.0-DEV.1039"
99103
_contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = UnitRange{Int}(r)

test/runtests.jl

+31
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,25 @@ end
345345
@test_throws BoundsError r[false:true:false]
346346
end
347347
end
348+
349+
@testset "iteration" begin
350+
# parent has Base.OneTo axes
351+
A = ones(4:10)
352+
ax = axes(A, 1)
353+
ind, st = iterate(ax)
354+
@test A[ind] == A[4]
355+
ind, st = iterate(ax, st)
356+
@test A[ind] == A[5]
357+
358+
# parent doesn't have Base.OneTo axes
359+
B = @view A[:]
360+
C = OffsetArray(B, 0)
361+
ax = axes(C, 1)
362+
ind, st = iterate(ax)
363+
@test C[ind] == C[4]
364+
ind, st = iterate(ax, st)
365+
@test C[ind] == C[5]
366+
end
348367
end
349368

350369
# used in testing the constructor
@@ -2472,3 +2491,15 @@ end
24722491
end
24732492

24742493
include("origin.jl")
2494+
2495+
@testset "misc" begin
2496+
@test OffsetArrays._subtractoffset(Base.OneTo(2), 1) isa AbstractUnitRange{Int}
2497+
@test OffsetArrays._subtractoffset(Base.OneTo(2), 1) == 0:1
2498+
@test OffsetArrays._subtractoffset(3:2:9, 1) isa AbstractRange{Int}
2499+
@test OffsetArrays._subtractoffset(3:2:9, 1) == 2:2:8
2500+
2501+
@test OffsetArrays._addoffset(Base.OneTo(2), 1) isa AbstractUnitRange{Int}
2502+
@test OffsetArrays._addoffset(Base.OneTo(2), 1) == 2:3
2503+
@test OffsetArrays._addoffset(3:2:9, 1) isa AbstractRange{Int}
2504+
@test OffsetArrays._addoffset(3:2:9, 1) == 4:2:10
2505+
end

0 commit comments

Comments
 (0)