Movatterモバイル変換


[0]ホーム

URL:


GitHub

SubArrays

Julia'sSubArray type is a container encoding a "view" of a parentAbstractArray. This page documents some of the design principles and implementation ofSubArrays.

One of the major design goals is to ensure high performance for views of bothIndexLinear andIndexCartesian arrays. Furthermore, views ofIndexLinear arrays should themselves beIndexLinear to the extent that it is possible.

Index replacement

Consider making 2d slices of a 3d array:

julia> A = rand(2,3,4);julia> S1 = view(A, :, 1, 2:3)2×2 view(::Array{Float64, 3}, :, 1, 2:3) with eltype Float64: 0.839622  0.711389 0.967143  0.103929julia> S2 = view(A, 1, :, 2:3)3×2 view(::Array{Float64, 3}, 1, :, 2:3) with eltype Float64: 0.839622  0.711389 0.789764  0.806704 0.566704  0.962715

view drops "singleton" dimensions (ones that are specified by anInt), so bothS1 andS2 are two-dimensionalSubArrays. Consequently, the natural way to index these is withS1[i,j]. To extract the value from the parent arrayA, the natural approach is to replaceS1[i,j] withA[i,1,(2:3)[j]] andS2[i,j] withA[1,i,(2:3)[j]].

The key feature of the design of SubArrays is that this index replacement can be performed without any runtime overhead.

SubArray design

Type parameters and fields

The strategy adopted is first and foremost expressed in the definition of the type:

struct SubArray{T,N,P,I,L} <: AbstractArray{T,N}    parent::P    indices::I    offset1::Int       # for linear indexing and pointer, only valid when L==true    stride1::Int       # used only for linear indexing    ...end

SubArray has 5 type parameters. The first two are the standard element type and dimensionality. The next is the type of the parentAbstractArray. The most heavily-used is the fourth parameter, aTuple of the types of the indices for each dimension. The final one,L, is only provided as a convenience for dispatch; it's a boolean that represents whether the index types support fast linear indexing. More on that later.

If in our example aboveA is aArray{Float64, 3}, ourS1 case above would be aSubArray{Float64,2,Array{Float64,3},Tuple{Base.Slice{Base.OneTo{Int64}},Int64,UnitRange{Int64}},false}. Note in particular the tuple parameter, which stores the types of the indices used to createS1. Likewise,

julia> S1.indices(Base.Slice(Base.OneTo(2)), 1, 2:3)

Storing these values allows index replacement, and having the types encoded as parameters allows one to dispatch to efficient algorithms.

Index translation

Performing index translation requires that you do different things for different concreteSubArray types. For example, forS1, one needs to apply thei,j indices to the first and third dimensions of the parent array, whereas forS2 one needs to apply them to the second and third. The simplest approach to indexing would be to do the type-analysis at runtime:

parentindices = Vector{Any}()for thisindex in S.indices    ...    if isa(thisindex, Int)        # Don't consume one of the input indices        push!(parentindices, thisindex)    elseif isa(thisindex, AbstractVector)        # Consume an input index        push!(parentindices, thisindex[inputindex[j]])        j += 1    elseif isa(thisindex, AbstractMatrix)        # Consume two input indices        push!(parentindices, thisindex[inputindex[j], inputindex[j+1]])        j += 2    elseif ...endS.parent[parentindices...]

Unfortunately, this would be disastrous in terms of performance: each element access would allocate memory, and involves the running of a lot of poorly-typed code.

The better approach is to dispatch to specific methods to handle each type of stored index. That's whatreindex does: it dispatches on the type of the first stored index and consumes the appropriate number of input indices, and then it recurses on the remaining indices. In the case ofS1, this expands to

Base.reindex(S1, S1.indices, (i, j)) == (i, S1.indices[2], S1.indices[3][j])

for any pair of indices(i,j) (exceptCartesianIndexs and arrays thereof, see below).

This is the core of aSubArray; indexing methods depend uponreindex to do this index translation. Sometimes, though, we can avoid the indirection and make it even faster.

Linear indexing

Linear indexing can be implemented efficiently when the entire array has a single stride that separates successive elements, starting from some offset. This means that we can pre-compute these values and represent linear indexing simply as an addition and multiplication, avoiding the indirection ofreindex and (more importantly) the slow computation of the cartesian coordinates entirely.

ForSubArray types, the availability of efficient linear indexing is based purely on the types of the indices, and does not depend on values like the size of the parent array. You can ask whether a given set of indices supports fast linear indexing with the internalBase.viewindexing function:

julia> Base.viewindexing(S1.indices)IndexCartesian()julia> Base.viewindexing(S2.indices)IndexLinear()

This is computed during construction of theSubArray and stored in theL type parameter as a boolean that encodes fast linear indexing support. While not strictly necessary, it means that we can define dispatch directly onSubArray{T,N,A,I,true} without any intermediaries.

Since this computation doesn't depend on runtime values, it can miss some cases in which the stride happens to be uniform:

julia> A = reshape(1:4*2, 4, 2)4×2 reshape(::UnitRange{Int64}, 4, 2) with eltype Int64: 1  5 2  6 3  7 4  8julia> diff(A[2:2:4,:][:])3-element Vector{Int64}: 2 2 2

A view constructed asview(A, 2:2:4, :) happens to have uniform stride, and therefore linear indexing indeed could be performed efficiently. However, success in this case depends on the size of the array: if the first dimension instead were odd,

julia> A = reshape(1:5*2, 5, 2)5×2 reshape(::UnitRange{Int64}, 5, 2) with eltype Int64: 1   6 2   7 3   8 4   9 5  10julia> diff(A[2:2:4,:][:])3-element Vector{Int64}: 2 3 2

thenA[2:2:4,:] does not have uniform stride, so we cannot guarantee efficient linear indexing. Since we have to base this decision based purely on types encoded in the parameters of theSubArray,S = view(A, 2:2:4, :) cannot implement efficient linear indexing.

A few details

Settings


This document was generated withDocumenter.jl version 1.8.0 onWednesday 9 July 2025. Using Julia version 1.11.6.


[8]ページ先頭

©2009-2025 Movatter.jp