4

我一直在用头撞墙,试图在 Julia 中使用静态数组。

https://github.com/JuliaArrays/StaticArrays.jl

它们速度很快,但更新它们很痛苦。这并不奇怪,它们注定是不可变的!

但不断有人建议我使用静态数组,即使我必须更新它们。在我的例子中,静态数组很小,只有 3 个长度,我有一个向量,但我一次只更新 1 个长度的三个向量。


选项1

有一个非常简洁的包叫做Setfield,它允许你在 Julia 中对 SVectors 进行就地更新。

https://github.com/jw3126/Setfield.jl

问题...它更新了本地副本。因此,如果您在嵌套函数中,它会更新本地副本。因此,它带有一些簿记,因为您必须就地更新本地副本,然后return该副本并更新实际感兴趣的数组。你不能传入你想要的数组并更新它,至少,我不能弄清楚!现在,我不介意簿记,但我觉得更新本地副本,然后返回值,更新另一个本地副本,然后返回值,最后更新实际数组必须带来速度损失。我可能是错的。


选项 2

让我感到困扰的是,为了更新静态数组,我必须

exampleSVector::SVector{3,Float64}<-- 只是为了明确它的类型和大小

exampleSVector = [value1, value2, value3]

这将更新所需的数组,即使它在函数内部,这很好,也是目标,但如果你在函数内部执行此操作,它会创建一个临时数组。这要了我的命,因为我的函数处于一个被调用 4+ 百万次的循环中,所以这会产生大量的分配并减慢速度。


如何在SVector不创建临时数组的情况下更新选项 2 场景?

对于选项 1 场景,我可以更新实际感兴趣的数组而不是本地副本吗?

如果这需要一个简单的示例代码,请在评论中说明,我会做一个。我的想法是没有一个是可以回答的,但是如果需要的话我会做一个。

编辑:

MCVE 代码 - 选项 1 有效,选项 2 无效。

using Setfield
using StaticArrays

struct Keep
    dreaming::Vector{SVector{3,Float64}}
end

function INNER!(vec::SVector{3,Float64},pre::SVector{3,Float64})
    # pretend series of calculations
    for i = 1:3 # illustrate use of Setfield (used in real code for this)
        pre = @set pre[i] = rand() * i * 1000
    end

    # more pretend calculations
    x = 25.0 # assume more calculations equals x
################## OPTION 1 ########################
    vec = @set vec = x * [ pre[1], pre[2], pre[3] ] # UNCOMMENT FOR FOR OPTION 1
    return vec                                      # UNCOMMENT FOR FOR OPTION 1

################## OPTION 2 ########################    
    #vec = x * [ pre[1], pre[2], pre[3] ]           # UNCOMMENT FOR FOR OPTION 2
    #nothing                                        # UNCOMMENT FOR FOR OPTION 2

end

function OUTER!(always::Keep)
    preAllocate = SVector{3}(0.0,0.0,0.0)

    for i=1:length(always.dreaming)

        always.dreaming[i] = INNER!(always.dreaming[i], preAllocate) # UNCOMMENT FOR FOR OPTION 1
        #INNER!(always.dreaming[i], preAllocate)                     # UNCOMMENT FOR FOR OPTION 2
    end
end
code = Keep([zero(SVector{3}) for i=1:5])

OUTER!(code)
println(code.dreaming)
4

2 回答 2

3

我希望我正确理解了你的问题。像这样的 MWE 有点难,它会做很多多余的事情,而且有点令人困惑。

这里似乎有两种替代解释:要么你真的需要更新 ('mutate') an SVector,但你的 MWE 未能证明原因。或者,您已经说服自己需要变异,但实际上不需要。

我决定专注于备选方案 2:您实际上并不需要“变异”。从这个角度重写你的代码可以大大简化它。

我找不到任何理由让你在这里改变任何静态向量,所以我只是删除了它。INNER!带有输入的函数的行为非常令人困惑。您提供了两个输入,但不使用其中任何一个,所以我删除了这些输入。

function inner()
    pre = @SVector [rand() * 1000i for i in 1:3]
    x = 25
    return pre .* x
end

function outer!(always::Keep)
    always.dreaming .= inner.()  # notice the dot in inner.()
end

code = Keep([zero(SVector{3}) for i in 1:5])
outer!(code)
display(code.dreaming)

这运行速度快,分配为零。一般来说,使用 StaticArrays,不要尝试改变事物,只需创建新实例。

尽管您的 MWE 并不清楚,但您可能有一些正当理由想要“变异”一个SVector. 这种情况下可以使用StaticArrays的setindex方法,不需要Setfield.jl:

julia> v = rand(SVector{3})
3-element SArray{Tuple{3},Float64,1,3}:
 0.4730258499237898 
 0.23658547518737905
 0.9140206579322541 

julia> v = setindex(v, -3.1, 2)
3-element SArray{Tuple{3},Float64,1,3}:
  0.4730258499237898
 -3.1               
  0.9140206579322541

澄清:(setindex没有 a !)不会改变其输入,而是创建一个更改了一个索引值的新实例。

如果您确实需要“变异”,也许您可​​以制作一个新的 MWE 来显示这一点。我建议您尝试简化一下,因为它现在很混乱。例如,包含类型Keep似乎完全没有必要和分散注意力。只需制作 a Vectorof SVectors 并展示您想用它做什么。

编辑:这是基于以下评论的尝试。据我现在了解,问题是关于修改SVectors 的向量。您不能真正改变SVectors,但您可以使用方便的语法替换它们setindex,您可以在其中保留一些元素并更改其他一些元素:

oldvec = [zero(SVector{3}) for _ in 1:5]
replacevec = [rand(SVector{3}) for _ in 1:5]

现在我们将 的每个元素的第二个元素替换为 中oldvec的对应元素replacevec。首先是单线:

oldvec .= setindex.(oldvec, getindex.(replacevec, 2), 2)

然后是一个更快的循环:

for i in eachindex(oldvec, replacevec)
    @inbounds oldvec[i] = setindex(oldvec[i], replacevec[i][2], 2)
end
于 2019-02-17T11:56:37.430 回答
2

有两种类型的静态数组 - 可变(以Min 类型名称开头)和不可变数组(以 开头S) - 只需使用可变数组!看看下面的例子:

julia> mut = MVector{3,Int64}(1:3);

julia> mut[1]=55
55

julia> mut
3-element MArray{Tuple{3},Int64,1,3}:
 55
  2
  3

julia> immut = SVector{3,Int64}(1:3);

julia> inmut[1]=55
ERROR: setindex!(::SArray{Tuple{3},Int64,1,3}, value, ::Int) is not defined.

让我们看一些简单的基准测试(普通数组,可变静态 vs 不可变静态):

using BenchmarkTools

julia> ord = [1,2,3];

julia> @btime $ord.*$ord;
  39.680 ns (1 allocation: 112 bytes)
3-element Array{Int64,1}:
 1
 4
 9



julia> @btime $mut.*$mut
  8.533 ns (1 allocation: 32 bytes)
3-element MArray{Tuple{3},Int64,1,3}:
 3025
    4
    9


julia> @btime $immut.*$immut
  2.133 ns (0 allocations: 0 bytes)
3-element SArray{Tuple{3},Int64,1,3}:
 1
 4
 9
于 2019-02-16T20:18:07.253 回答