Skip to content

Commit

Permalink
Adapt AbstractArray subtypes for indexing fallbacks
Browse files Browse the repository at this point in the history
This removes redundant behaviors from existing AbstractArray subtypes.
  • Loading branch information
mbauman committed Jun 3, 2015
1 parent b77b026 commit 5cb2835
Show file tree
Hide file tree
Showing 14 changed files with 156 additions and 616 deletions.
15 changes: 7 additions & 8 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,13 @@ macro _noinline_meta()
end

## Bounds checking ##
_checkbounds(sz::Int, i::Int) = 1 <= i <= sz
_checkbounds(sz::Int, i::Real) = (@_inline_meta; _checkbounds(sz, to_index(i)))
_checkbounds(sz::Int, I::AbstractVector{Bool}) = length(I) == sz
_checkbounds(sz::Int, I::AbstractArray{Bool}) = length(I) == sz # setindex! allows this
_checkbounds(sz::Int, r::Range{Int}) = (@_inline_meta; isempty(r) || (minimum(r) >= 1 && maximum(r) <= sz))
_checkbounds{T<:Real}(sz::Int, r::Range{T}) = (@_inline_meta; _checkbounds(sz, to_index(r)))
_checkbounds(sz::Int, ::Colon) = true
function _checkbounds{T <: Real}(sz::Int, I::AbstractArray{T})
_checkbounds(sz, i::Integer) = 1 <= i <= sz
_checkbounds(sz, i::Real) = 1 <= to_index(i) <= sz
_checkbounds(sz, I::AbstractVector{Bool}) = length(I) == sz
_checkbounds(sz, r::Range{Int}) = (@_inline_meta; isempty(r) || (minimum(r) >= 1 && maximum(r) <= sz))
_checkbounds{T<:Real}(sz, r::Range{T}) = (@_inline_meta; _checkbounds(sz, to_index(r)))
_checkbounds(sz, ::Colon) = true
function _checkbounds{T <: Real}(sz, I::AbstractArray{T})
@_inline_meta
b = true
for i in I
Expand Down
165 changes: 39 additions & 126 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -293,111 +293,47 @@ done(a::Array,i) = (i > length(a))

## Indexing: getindex ##

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

getindex(A::Array, i0::Real) = arrayref(A,to_index(i0))
getindex(A::Array, i0::Real, i1::Real) = arrayref(A,to_index(i0),to_index(i1))
getindex(A::Array, i0::Real, i1::Real, i2::Real) =
arrayref(A,to_index(i0),to_index(i1),to_index(i2))
getindex(A::Array, i0::Real, i1::Real, i2::Real, i3::Real) =
arrayref(A,to_index(i0),to_index(i1),to_index(i2),to_index(i3))
getindex(A::Array, i0::Real, i1::Real, i2::Real, i3::Real, i4::Real) =
arrayref(A,to_index(i0),to_index(i1),to_index(i2),to_index(i3),to_index(i4))
getindex(A::Array, i0::Real, i1::Real, i2::Real, i3::Real, i4::Real, i5::Real) =
arrayref(A,to_index(i0),to_index(i1),to_index(i2),to_index(i3),to_index(i4),to_index(i5))

getindex(A::Array, i0::Real, i1::Real, i2::Real, i3::Real, i4::Real, i5::Real, I::Real...) =
arrayref(A,to_index(i0),to_index(i1),to_index(i2),to_index(i3),to_index(i4),to_index(i5),to_index(I)...)

# Fast copy using copy! for UnitRange
function getindex(A::Array, I::UnitRange{Int})
getindex(A::Array, i1::Int) = arrayref(A, i1)
unsafe_getindex(A::Array, i1::Int) = @inbounds return arrayref(A, i1)

# Faster contiguous indexing using copy! for UnitRange and Colon
getindex(A::Array, I::UnitRange{Int}) = (checkbounds(A, I); unsafe_getindex(A, I))
function unsafe_getindex(A::Array, I::UnitRange{Int})
lI = length(I)
X = similar(A, lI)
if lI > 0
copy!(X, 1, A, first(I), lI)
unsafe_copy!(X, 1, A, first(I), lI)
end
return X
end

function getindex{T<:Real}(A::Array, I::AbstractVector{T})
return [ A[i] for i in to_index(I) ]
end
function getindex{T<:Real}(A::Range, I::AbstractVector{T})
return [ A[i] for i in to_index(I) ]
end
function getindex(A::Range, I::AbstractVector{Bool})
checkbounds(A, I)
return [ A[i] for i in to_index(I) ]
end

function getindex(A::Array, ::Colon)
return [ a for a in A ]
end

# logical indexing
# (when the indexing is provided as an Array{Bool} or a BitArray we can be
# sure about the behaviour and use unsafe_getindex; in the general case
# we can't and must use getindex, otherwise silent corruption can happen)

@generated function getindex_bool_1d(A::Array, I::AbstractArray{Bool})
idxop = I <: Union(Array{Bool}, BitArray) ? :unsafe_getindex : :getindex
quote
checkbounds(A, I)
n = sum(I)
out = similar(A, n)
c = 1
for i = 1:length(I)
if $idxop(I, i)
@inbounds out[c] = A[i]
c += 1
end
end
out
getindex(A::Array, c::Colon) = unsafe_getindex(A, c)
function unsafe_getindex(A::Array, ::Colon)
lI = length(A)
X = similar(A, lI)
if lI > 0
unsafe_copy!(X, 1, A, 1, lI)
end
return X
end

getindex(A::Vector, I::AbstractVector{Bool}) = getindex_bool_1d(A, I)
getindex(A::Vector, I::AbstractArray{Bool}) = getindex_bool_1d(A, I)
getindex(A::Array, I::AbstractVector{Bool}) = getindex_bool_1d(A, I)
getindex(A::Array, I::AbstractArray{Bool}) = getindex_bool_1d(A, I)

# This is redundant with the abstract fallbacks, but needed for bootstrap
function getindex{T<:Real}(A::Array, I::Range{T})
return [ A[to_index(i)] for i in I ]
end

## Indexing: setindex! ##
setindex!{T}(A::Array{T}, x) = arrayset(A, convert(T,x), 1)

setindex!{T}(A::Array{T}, x, i0::Real) = arrayset(A, convert(T,x), to_index(i0))
setindex!{T}(A::Array{T}, x, i0::Real, i1::Real) =
arrayset(A, convert(T,x), to_index(i0), to_index(i1))
setindex!{T}(A::Array{T}, x, i0::Real, i1::Real, i2::Real) =
arrayset(A, convert(T,x), to_index(i0), to_index(i1), to_index(i2))
setindex!{T}(A::Array{T}, x, i0::Real, i1::Real, i2::Real, i3::Real) =
arrayset(A, convert(T,x), to_index(i0), to_index(i1), to_index(i2), to_index(i3))
setindex!{T}(A::Array{T}, x, i0::Real, i1::Real, i2::Real, i3::Real, i4::Real) =
arrayset(A, convert(T,x), to_index(i0), to_index(i1), to_index(i2), to_index(i3), to_index(i4))
setindex!{T}(A::Array{T}, x, i0::Real, i1::Real, i2::Real, i3::Real, i4::Real, i5::Real) =
arrayset(A, convert(T,x), to_index(i0), to_index(i1), to_index(i2), to_index(i3), to_index(i4), to_index(i5))
setindex!{T}(A::Array{T}, x, i0::Real, i1::Real, i2::Real, i3::Real, i4::Real, i5::Real, I::Real...) =
arrayset(A, convert(T,x), to_index(i0), to_index(i1), to_index(i2), to_index(i3), to_index(i4), to_index(i5), to_index(I)...)

function setindex!{T<:Real}(A::Array, x, I::AbstractVector{T})

# These are redundant with the abstract fallbacks but needed for bootstrap
function setindex!(A::Array, x, I::AbstractVector{Int})
is(A, I) && (I = copy(I))
for i in I
A[i] = x
end
return A
end

function setindex!{T}(A::Array{T}, X::Array{T}, I::UnitRange{Int})
if length(X) != length(I)
throw_setindex_mismatch(X, (I,))
end
copy!(A, first(I), X, 1, length(I))
return A
end

function setindex!{T<:Real}(A::Array, X::AbstractArray, I::AbstractVector{T})
if length(X) != length(I)
throw_setindex_mismatch(X, (I,))
end
function setindex!(A::Array, X::AbstractArray, I::AbstractVector{Int})
setindex_shape_check(X, length(I))
count = 1
if is(X,A)
X = copy(X)
Expand All @@ -412,49 +348,26 @@ function setindex!{T<:Real}(A::Array, X::AbstractArray, I::AbstractVector{T})
return A
end

setindex!(A::Array, x, I::Colon) = setindex!(A, x, 1:length(A))

# logical indexing
# (when the indexing is provided as an Array{Bool} or a BitArray we can be
# sure about the behaviour and use unsafe_getindex; in the general case
# we can't and must use getindex, otherwise silent corruption can happen)

@generated function assign_bool_scalar_1d!(A::Array, x, I::AbstractArray{Bool})
idxop = I <: Union(Array{Bool}, BitArray) ? :unsafe_getindex : :getindex
quote
checkbounds(A, I)
for i = 1:length(I)
if $idxop(I, i)
@inbounds A[i] = x
end
end
A
# Faster contiguous setindex! with copy!
setindex!{T}(A::Array{T}, X::Array{T}, I::UnitRange{Int}) = (checkbounds(A, I); unsafe_setindex!(A, X, I))
function unsafe_setindex!{T}(A::Array{T}, X::Array{T}, I::UnitRange{Int})
lI = length(I)
setindex_shape_check(X, lI)
if lI > 0
unsafe_copy!(A, first(I), X, 1, lI)
end
return A
end

@generated function assign_bool_vector_1d!(A::Array, X::AbstractArray, I::AbstractArray{Bool})
idxop = I <: Union(Array{Bool}, BitArray) ? :unsafe_getindex : :getindex
quote
checkbounds(A, I)
c = 1
for i = 1:length(I)
if $idxop(I, i)
x = X[c]
@inbounds A[i] = x
c += 1
end
end
if length(X) != c-1
throw(DimensionMismatch("assigned $(length(X)) elements to length $(c-1) destination"))
end
A
setindex!{T}(A::Array{T}, X::Array{T}, c::Colon) = unsafe_setindex!(A, X, c)
function unsafe_setindex!{T}(A::Array{T}, X::Array{T}, ::Colon)
lI = length(A)
setindex_shape_check(X, lI)
if lI > 0
unsafe_copy!(A, 1, X, 1, lI)
end
return A
end

setindex!(A::Array, X::AbstractArray, I::AbstractVector{Bool}) = assign_bool_vector_1d!(A, X, I)
setindex!(A::Array, X::AbstractArray, I::AbstractArray{Bool}) = assign_bool_vector_1d!(A, X, I)
setindex!(A::Array, x, I::AbstractVector{Bool}) = assign_bool_scalar_1d!(A, x, I)
setindex!(A::Array, x, I::AbstractArray{Bool}) = assign_bool_scalar_1d!(A, x, I)

# efficiently grow an array

Expand Down
119 changes: 18 additions & 101 deletions base/bitarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,60 +348,8 @@ bitpack{T,N}(A::AbstractArray{T,N}) = convert(BitArray{N}, A)
return r
end

@inline function getindex(B::BitArray, i::Int)
1 <= i <= length(B) || throw(BoundsError(B, i))
return unsafe_bitgetindex(B.chunks, i)
end

getindex(B::BitArray, i::Real) = getindex(B, to_index(i))

getindex(B::BitArray) = getindex(B, 1)

# 0d bitarray
getindex(B::BitArray{0}) = unsafe_bitgetindex(B.chunks, 1)

function getindex{T<:Real}(B::BitArray, I::AbstractVector{T})
X = BitArray(length(I))
lB = length(B)
Xc = X.chunks
Bc = B.chunks
ind = 1
for i in I
# faster X[ind] = B[i]
j = to_index(i)
1 <= j <= lB || throw(BoundsError(B, j))
unsafe_bitsetindex!(Xc, unsafe_bitgetindex(Bc, j), ind)
ind += 1
end
return X
end

# logical indexing
# (when the indexing is provided as an Array{Bool} or a BitArray we can be
# sure about the behaviour and use unsafe_getindex; in the general case
# we can't and must use getindex, otherwise silent corruption can happen)
# (multiple signatures for disambiguation)
for IT in [AbstractVector{Bool}, AbstractArray{Bool}]
@eval @generated function getindex(B::BitArray, I::$IT)
idxop = I <: Union(Array{Bool}, BitArray) ? :unsafe_getindex : :getindex
quote
checkbounds(B, I)
n = sum(I)
X = BitArray(n)
Xc = X.chunks
Bc = B.chunks
ind = 1
for i = 1:length(I)
if $idxop(I, i)
# faster X[ind] = B[i]
unsafe_bitsetindex!(Xc, unsafe_bitgetindex(Bc, i), ind)
ind += 1
end
end
return X
end
end
end
@inline getindex(B::BitArray, i::Int) = (checkbounds(B, i); unsafe_getindex(B, i))
@inline unsafe_getindex(B::BitArray, i::Int) = unsafe_bitgetindex(B.chunks, i)

## Indexing: setindex! ##

Expand All @@ -417,11 +365,9 @@ end
end
end

setindex!(B::BitArray, x) = setindex!(B, convert(Bool,x), 1)

function setindex!(B::BitArray, x::Bool, i::Int)
1 <= i <= length(B) || throw(BoundsError(B, i))
unsafe_bitsetindex!(B.chunks, x, i)
setindex!(B::BitArray, x, i::Int) = (checkbounds(B, i); unsafe_setindex!(B, x, i))
@inline function unsafe_setindex!(B::BitArray, x, i::Int)
unsafe_bitsetindex!(B.chunks, convert(Bool, x), i)
return B
end

Expand All @@ -430,12 +376,13 @@ end
# sure about the behaviour and use unsafe_getindex; in the general case
# we can't and must use getindex, otherwise silent corruption can happen)

function setindex!(B::BitArray, x, I::BitArray)
checkbounds(B, I)
# When indexing with a BitArray, we can operate whole chunks at a time for a ~100x gain
setindex!(B::BitArray, x, I::BitArray) = (checkbounds(B, I); unsafe_setindex!(B, x, I))
function unsafe_setindex!(B::BitArray, x, I::BitArray)
y = convert(Bool, x)
Bc = B.chunks
Ic = I.chunks
@assert length(Bc) == length(Ic)
length(Bc) == length(Ic) || throw_boundserror(B, I)
@inbounds if y
for i = 1:length(Bc)
Bc[i] |= Ic[i]
Expand All @@ -448,26 +395,15 @@ function setindex!(B::BitArray, x, I::BitArray)
return B
end

@generated function setindex!(B::BitArray, x, I::AbstractArray{Bool})
idxop = I <: Array{Bool} ? :unsafe_getindex : :getindex
quote
checkbounds(B, I)
y = convert(Bool, x)
Bc = B.chunks
for i = 1:length(I)
# faster I[i] && B[i] = y
$idxop(I, i) && unsafe_bitsetindex!(Bc, y, i)
end
return B
end
end

function setindex!(B::BitArray, X::AbstractArray, I::BitArray)
checkbounds(B, I)
# Assigning an array of bools is more complicated, but we can still do some
# work on chunks by combining X and I 64 bits at a time to improve perf by ~40%
setindex!(B::BitArray, X::AbstractArray, I::BitArray) = (checkbounds(B, I); unsafe_setindex!(B, X, I))
function unsafe_setindex!(B::BitArray, X::AbstractArray, I::BitArray)
Bc = B.chunks
Ic = I.chunks
@assert length(Bc) == length(Ic)
length(Bc) == length(Ic) || throw_boundserror(B, I)
lc = length(Bc)
lx = length(X)
last_chunk_len = Base._mod64(length(B)-1)+1

c = 1
Expand All @@ -477,7 +413,8 @@ function setindex!(B::BitArray, X::AbstractArray, I::BitArray)
u = UInt64(1)
for j = 1:(i < lc ? 64 : last_chunk_len)
if Imsk & u != 0
x = convert(Bool, X[c])
lx < c && throw_setindex_mismatch(X, c)
x = convert(Bool, unsafe_getindex(X, c))
if x
C |= u
else
Expand All @@ -490,31 +427,11 @@ function setindex!(B::BitArray, X::AbstractArray, I::BitArray)
@inbounds Bc[i] = C
end
if length(X) != c-1
throw(DimensionMismatch("assigned $(length(X)) elements to length $(c-1) destination"))
throw_setindex_mismatch(X, c-1)
end
return B
end

@generated function setindex!(B::BitArray, X::AbstractArray, I::AbstractArray{Bool})
idxop = I <: Array{Bool} ? :unsafe_getindex : :getindex
quote
checkbounds(B, I)
Bc = B.chunks
c = 1
for i = 1:length(I)
if $idxop(I, i)
# faster B[i] = X[c]
unsafe_bitsetindex!(Bc, convert(Bool, X[c]), i)
c += 1
end
end
if length(X) != c-1
throw(DimensionMismatch("assigned $(length(X)) elements to length $(c-1) destination"))
end
return B
end
end

## Dequeue functionality ##

function push!(B::BitVector, item)
Expand Down
Loading

0 comments on commit 5cb2835

Please sign in to comment.