想到朱莉娅的performance tips我还没有找到任何有关如何使用三维数组加速代码的建议。
根据我的理解d-element Array{Array{Float64,2},1}
在d
(第三维)很小时表现最佳。但是,我不确定当d
很大时是否就是这种情况。
Julia有没有关于这个主题的教程?
示例1a(d = 50)
x = [zeros(100, 10) for d=1:50];
@time for d=1:50
x[d] = rand(100,10);
end
0.000100 seconds (50 allocations: 396.875 KB)
示例1b(d = 50)
y=zeros(100, 10, 50);
@time for d=1:50
y[:,:,d] = rand(100,10);
end
0.000257 seconds (200 allocations: 400.781 KB)
示例2a(d = 50000)
x = [zeros(100, 10) for d=1:50000];
@time for d=1:50000
x[d] = rand(100,10);
end
0.410813 seconds (99.49 k allocations: 388.328 MB, 81.88% gc time)
示例2b(d = 50000)
y=zeros(100, 10, 50000);
@time for d=1:50000
y[:,:,d] = rand(100,10);
end
0.185929 seconds (298.98 k allocations: 392.898 MB, 6.83% gc time)
答案 0 :(得分:4)
根据我的理解,当d(第三维)很小时,d-element Array {Array {Float64,2},1}表现最佳。但是,我不确定当d很大时是否就是这种情况。
不,它更像是你如何使用它。 A = Array{Array{Float64,2},1}
是指向矩阵的指针数组。数组的值是指针或引用。因此A[i]
返回一个引用,即它很便宜。 A2 = Array{Float64,3}
是一个连续的浮点数组。它实际上只是一个线性平板内存的索引设置(并且具有线性索引A2[i]
,它使用该线性形式贯穿整个事物)。
后者有一些优点,因为它是连续的。没有间接,因此循环遍历所有A2
的值会更快。 A
必须使用两个指针来获取值,因此如果您不知道仅将每个内部矩阵推迟一次,则简单的3D循环会更慢。此外,您可以通过@view A2[:,:,1]
等获取矩阵的视图,但您必须注意A2[:,:,1]
本身将生成矩阵的副本。 A[1]
自然是一个视图,因为它返回对matirx的引用,如果要复制,则必须明确地执行copy(A[1])
。因为A
只是一个线性的指针数组,push!
一个新的矩阵很便宜,因为它只是增加一个相对较小的数组(push!
自动摊销)来添加一个新的最后的指针(这就是DifferentialEqautions.jl之类的东西使用数组数组来构建时间序列而不是更传统的矩阵的原因。
所以它们是不同的工具,具有不同的优点和缺点。
至于你的时间,你做了两件不同的事情。 x[d] = rand(100,10)
正在创建一个新矩阵,并将其引用添加到x
。 y[:,:,d] = rand(100,10)
正在创建一个新矩阵并循环显示y
的值以更改y
的值。你可以看到为什么那么慢。但是你要忽略的是无分配案例。
function f2()
y=zeros(100, 10, 50);
@time for i in eachindex(y)
y[i] = rand()
end
y
end
在小例中,这与数组创建匹配。你不能天真地在第一种情况下这样做,但正如我所说,如果你做得非常好,你取消引用矩阵的指针:
function f()
x = [zeros(100, 10) for d=1:5000];
@time @inbounds for d=1:50
xd = x[d]
for i in eachindex(xd)
xd[i] = rand()
end
end
x
end
因此,在正确的情况下,数组数组可以是很好的数据结构。创建库RecursiveArrayTools.jl是为了更好地利用它。例如,A3 = VectorOfArrays(A)
通过延迟转换A3
到A2
,为A[i,j,k]
提供与A[k][i,j]
相同的索引结构。但是,它保留了A
的优点,但会自动确保以f
之类的正确方式进行广播。像这样的另一个工具是ArrayPartition
,它允许以广播性能的方式进行异构键入。
所以是的,它并不总是正确的工具,但正确使用这些异构和递归数组是很好的工具。