Skip to content

Bounds-checking overhead for an IdOffsetRange #214

@jishnub

Description

@jishnub

Bounds-checking is currently considerably more expensive with an IdOffsetRange than with a Base.OneTo. For example, depending on how we define setindex!:

julia> x = zeros(Int, 10_000);

julia> y = OffsetVector(x, 0);

julia> fill1d(x) = for i in axes(x,1); x[i] = i; end

julia> @btime fill1d($x);
  10.634 μs (0 allocations: 0 bytes)

# Current definition
@inline function Base.setindex!(A::OffsetVector, val, i::Int)
    @boundscheck checkbounds(A, i)
    @inbounds parent(A)[parentindex(Base.axes1(A), i)] = val
    A
end

julia> @btime fill1d($y);
  16.032 μs (0 allocations: 0 bytes)

# Alternate definition where bounds checking is forwarded to the parent
@propagate_inbounds function Base.setindex!(A::OffsetVector, val, i::Int)
    parent(A)[parentindex(Base.axes1(A), i)] = val
    A
end

julia> @btime fill1d($y);
  12.505 μs (0 allocations: 0 bytes)

The present definition was only introduced to perform bounds-checking at the top level so that error messages reflect the indices of the OffsetArray and not that of the parent. However this appears to come with a significant performance penalty.

The trend also persists with larger arrays.

julia> x = zeros(Int, 10_000_000);

julia> y = OffsetVector(x, 0);

julia> @btime fill1d($x);
  11.920 ms (0 allocations: 0 bytes)

julia> @btime fill1d($y); # present definition
  17.342 ms (0 allocations: 0 bytes)

julia> @btime fill1d($y); # forward bounds checking to the parent 
  13.341 ms (0 allocations: 0 bytes)

Also: why is bounds-checking so expensive? Is it being performed per element? Should it not be possible to perform a bounds-check for the axis in one-go, and not per element?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions