SubArrays
Julia'nın SubArray
türü, bir ana AbstractArray
'nın "görünümünü" kodlayan bir konteynerdir. Bu sayfa, SubArray
'lerin bazı tasarım ilkelerini ve uygulamalarını belgeler.
Birincil tasarım hedeflerinden biri, hem IndexLinear
hem de IndexCartesian
dizilerinin görünümleri için yüksek performans sağlamaktır. Ayrıca, IndexLinear
dizilerinin görünümleri mümkün olduğunca IndexLinear
olmalıdır.
Index replacement
2d dilimlerini 3d bir diziden oluşturmayı düşünün:
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.103929
julia> 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
"singleton" boyutları (bir Int
ile belirtilen) kaldırır, bu nedenle hem S1
hem de S2
iki boyutlu SubArray
'lerdir. Sonuç olarak, bunları indekslemenin doğal yolu S1[i,j]
şeklindedir. Ana dizi A
'dan değeri çıkarmak için doğal yaklaşım, S1[i,j]
'yi A[i,1,(2:3)[j]]
ile ve S2[i,j]
'yi A[1,i,(2:3)[j]]
ile değiştirmektir.
Alt dizaynının ana özelliği, bu indeks değişiminin herhangi bir çalışma zamanı yükü olmadan gerçekleştirilebilmesidir.
SubArray design
Type parameters and fields
Benimsenen strateji öncelikle türün tanımında ifade edilmektedir:
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
5 tür parametresine sahiptir. İlk ikisi standart eleman türü ve boyutsallıktır. Üçüncüsü, üst AbstractArray
türüdür. En çok kullanılan dördüncü parametre, her boyut için indeks türlerinin bir Tuple
'ıdır. Sonuncusu, L
, yalnızca dağıtım için bir kolaylık olarak sağlanır; hızlı lineer indekslemeyi destekleyip desteklemediğini temsil eden bir boole'dir. Daha sonra bununla ilgili daha fazla bilgi.
Eğer yukarıdaki örneğimizde A
bir Array{Float64, 3}
ise, yukarıdaki S1
durumu bir SubArray{Float64,2,Array{Float64,3},Tuple{Base.Slice{Base.OneTo{Int64}},Int64,UnitRange{Int64}},false}
olacaktır. Özellikle, S1
'i oluşturmak için kullanılan indekslerin türlerini saklayan tuple parametresine dikkat edin. Aynı şekilde,
julia> S1.indices
(Base.Slice(Base.OneTo(2)), 1, 2:3)
Bu değerleri depolamak, indeks değiştirmeye olanak tanır ve türlerin parametreler olarak kodlanması, verimli algoritmalara yönlendirme yapmayı sağlar.
Index translation
İndeks çevirisi yapmak, farklı somut SubArray
türleri için farklı şeyler yapmayı gerektirir. Örneğin, S1
için i,j
indekslerini ana dizinin birinci ve üçüncü boyutlarına uygulamak gerekirken, S2
için bunları ikinci ve üçüncü boyutlara uygulamak gerekir. İndeksleme için en basit yaklaşım, tür analizini çalışma zamanında yapmaktır:
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 ...
end
S.parent[parentindices...]
Maalesef, bu performans açısından felaket olurdu: her bir öğe erişimi bellek tahsis ederdi ve çok sayıda kötü tiplenmiş kodun çalıştırılmasını içerirdi.
Daha iyi yaklaşım, her tür saklanan indeksle başa çıkmak için belirli yöntemlere yönlendirmektir. reindex
işlevi de budur: ilk saklanan indeksin türüne göre yönlendirir ve uygun sayıda girdi indeksini tüketir, ardından kalan indeksler üzerinde yineleme yapar. S1
durumunda, bu genişler:
Base.reindex(S1, S1.indices, (i, j)) == (i, S1.indices[2], S1.indices[3][j])
herhangi bir indeks çifti (i,j)
için (aşağıda belirtilen CartesianIndex
ve dizileri hariç).
Bu, bir SubArray
'nin çekirdeğidir; indeksleme yöntemleri bu indeks çevirimi için reindex
'e dayanır. Ancak bazen dolaylı yoldan kaçınabiliriz ve bunu daha da hızlı hale getirebiliriz.
Linear indexing
Lineer indeksleme, tüm dizinin ardışık elemanları ayıran tek bir adım ile bir ofsetten başlayarak verimli bir şekilde uygulanabilir. Bu, bu değerleri önceden hesaplayabileceğimiz ve lineer indekslemeyi basit bir toplama ve çarpma olarak temsil edebileceğimiz anlamına gelir; böylece reindex
'in dolaylılığını ve (daha da önemlisi) kartezyen koordinatların yavaş hesaplanmasını tamamen önleyebiliriz.
SubArray
türleri için, verimli doğrusal indekslemenin mevcutlığı tamamen indekslerin türlerine dayanır ve ana dizinin boyutu gibi değerlere bağlı değildir. Belirli bir indeks kümesinin hızlı doğrusal indekslemeyi destekleyip desteklemediğini, dahili Base.viewindexing
fonksiyonu ile sorabilirsiniz:
julia> Base.viewindexing(S1.indices)
IndexCartesian()
julia> Base.viewindexing(S2.indices)
IndexLinear()
Bu, SubArray
'nın inşası sırasında hesaplanır ve hızlı doğrusal indeksleme desteğini kodlayan bir boolean olarak L
tür parametresinde saklanır. Kesinlikle gerekli olmamakla birlikte, SubArray{T,N,A,I,true}
üzerinde doğrudan dağıtım tanımlayabilmemizi sağlar.
Bu hesaplama çalışma zamanı değerlerine bağlı olmadığından, adımın tesadüfen uniform olduğu bazı durumları atlayabilir:
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 8
julia> diff(A[2:2:4,:][:])
3-element Vector{Int64}:
2
2
2
view(A, 2:2:4, :)
olarak oluşturulan bir görünüm, uniform stride'a sahip olduğundan, doğrusal indeksleme gerçekten de verimli bir şekilde gerçekleştirilebilir. Ancak, bu durumda başarı dizinin boyutuna bağlıdır: eğer ilk boyut tek olsaydı,
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 10
julia> diff(A[2:2:4,:][:])
3-element Vector{Int64}:
2
3
2
o zaman A[2:2:4,:]
uniform adım aralığına sahip değildir, bu nedenle verimli doğrusal indeksleme garantisi veremeyiz. Bu kararı tamamen SubArray
parametrelerinde kodlanmış türlere dayandırmamız gerektiğinden, S = view(A, 2:2:4, :)
verimli doğrusal indekslemeyi uygulayamaz.
A few details
Base.reindex
fonksiyonunun girdi indekslerinin türlerine kayıtsız olduğunu unutmayın; yalnızca saklanan indekslerin nasıl ve nerede yeniden indekslenmesi gerektiğini belirler. Bu, yalnızca tam sayı indekslerini desteklemekle kalmaz, aynı zamanda skalar olmayan indekslemeyi de destekler. Bu, görünümün görünümünün iki düzey dolaylılığa ihtiyaç duymadığı anlamına gelir; bunlar, yalnızca orijinal ana diziye indeksleri yeniden hesaplayabilir!Umarım şimdi destekleyen dilimlerin,
N
parametresiyle verilen boyutun, ana dizinin boyutuyla veyaindices
demetinin uzunluğuyla eşit olmayabileceği oldukça net olmuştur. Kullanıcı tarafından sağlanan indekslerin de mutlakaindices
demetindeki girişlerle örtüşmesi gerekmez (örneğin, ikinci kullanıcı tarafından sağlanan indeks ana dizinin üçüncü boyutuna karşılık gelebilir veindices
demetindeki üçüncü elemana denk gelebilir).Daha az belirgin olan şey, saklanan üst dizi boyutunun
indices
demetindeki etkili indeks sayısına eşit olması gerektiğidir. İşte bazı örnekler:A = reshape(1:35, 5, 7) # A 2d parent Array S = view(A, 2:7) # A 1d view created by linear indexing S = view(A, :, :, 1:1) # Appending extra indices is supported
Naif olarak,
S.parent = A
veS.indices = (:,:,1:1)
ayarlayabileceğinizi düşünebilirsiniz, ancak bunu desteklemek yeniden indeksleme sürecini dramatik bir şekilde karmaşıklaştırır, özellikle de görünümler için. Saklanan indekslerin türlerine göre dağıtım yapmanızın yanı sıra, belirli bir indeksin son indeks olup olmadığını incelemeniz ve kalan saklanan indeksleri "birleştirmeniz" gerekir. Bu kolay bir iş değil ve daha da kötüsü: bu, doğrusal indekslemeye dolaylı olarak bağlı olduğu için yavaştır.Neyse ki, bu tam olarak
ReshapedArray
'ın gerçekleştirdiği hesaplamadır ve mümkünse bunu lineer olarak yapar. Sonuç olarak,view
, ana dizinin verilen indeksler için uygun boyutta olmasını sağlamak amacıyla gerekirse onu yeniden şekillendirir. İçtekiSubArray
yapıcı, bu değişmezliğin sağlandığından emin olur.CartesianIndex
ve dizileri,reindex
şemasına kötü bir engel çıkarır.reindex
'in, saklanan indekslerin türüne göre kaç tane geçirilen indeksin kullanılacağını ve bunların nereye gideceğini belirlemek için basitçe dağıtım yaptığını hatırlayın. AncakCartesianIndex
ile, geçirilen argümanların sayısı ile bunların indekslediği boyutlar arasında artık birebir bir ilişki yoktur. YukarıdakiBase.reindex(S1, S1.indices, (i, j))
örneğine dönersek,i, j = CartesianIndex(), CartesianIndex(2,1)
için genişlemenin yanlış olduğunu görebilirsiniz. TamamenCartesianIndex()
'i atlamalı ve şunu döndürmelidir:(CartesianIndex(2,1)[1], S1.indices[2], S1.indices[3][CartesianIndex(2,1)[2]])
Bunun yerine, ama, şunu alıyoruz:
(CartesianIndex(), S1.indices[2], S1.indices[3][CartesianIndex(2,1)])
Bunu doğru yapmak, tüm boyut kombinasyonları boyunca hem saklanan hem de geçirilen indeksler üzerinde birleşik dağıtım gerektirecektir ve bu da yönetilemez bir şekilde olacaktır. Bu nedenle,
reindex
aslaCartesianIndex
indeksleri ile çağrılmamalıdır. Neyse ki, skalar durum,CartesianIndex
argümanlarını düz tam sayılara dönüştürerek kolayca işlenebilir. Ancak,CartesianIndex
dizileri bu kadar kolay bir şekilde ortogonal parçalara ayrılamaz.reindex
kullanmaya çalışmadan önce,view
, argüman listesindeCartesianIndex
dizileri olmadığından emin olmalıdır. Eğer varsa,reindex
hesaplamasını tamamen atlayarak iki seviyeli birSubArray
oluşturarak "topu taca atabilir".