我一直在用力撞墙,试图在茱莉亚中使用静态数组。
https://github.com/JuliaArrays/StaticArrays.jl
它们很快,但是更新它们很痛苦。这不足为奇,它们注定是不变的!
但是我一直建议我使用静态数组,即使我必须更新它们也是如此。就我而言,静态数组很小,只有3个长度,我有一个向量,但是我一次只更新1个长度三个SVector。
选项1
有一个名为Setfield
的简洁软件包,可用于在Julia中对SVectors进行就地更新。
https://github.com/jw3126/Setfield.jl
捕获...它将更新本地副本。因此,如果您使用的是嵌套函数,它将更新本地副本。因此,它带有一些记帐功能,因为您必须就地更新本地副本,然后return
复制并更新实际感兴趣的数组。至少不能传递您想要的数组并就地更新它,至少我无法弄清楚!现在,我不介意booboo,但是我想更新本地副本,然后返回值,更新另一个本地副本,然后返回值,最后更新实际数组,这必然会带来速度上的损失。我可能是错的。
选项2
令我烦恼的是,要更新静态数组,我必须
exampleSVector::SVector{3,Float64}
<-只是为了明确其类型和大小
exampleSVector = [value1, value2, value3]
这将更新所需的数组,即使它在函数内部也是如此,这是很不错的目标。但是,如果您在函数内部执行此操作,它将创建一个临时数组。这使我丧命,因为我的函数处于一个被称为4+百万次的循环中,因此这会创建大量的分配并减慢运行速度。
如何在不创建临时数组的情况下为选项2方案更新SVector
?
对于选项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)
答案 0 :(得分:2)
静态数组有两种类型-可变数组(类型名称以M
开头)和不可变数组(以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.
让我们看一些简单的基准(普通数组,可变静态与不可变静态):
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
答案 1 :(得分:1)
我希望我已正确理解您的问题。像这样的MWE有点困难,它做很多事情都是多余的,有些让人困惑。
这里似乎有两种替代解释:您确实需要更新(“变异”)一个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
(无!
)不会改变其输入,而是创建一个更改了一个索引值的新实例。
如果您确实需要“变异”,也许您可以制作一个新的MWE来显示此信息。我建议您尝试简化一下,因为它现在很混乱。例如,包含类型Keep
似乎是完全不必要和分散注意力的。只需制作Vector
个SVector
中的一个,然后显示您想要做什么。
编辑:这是基于以下评论的尝试。据我所了解,问题是关于修改SVector
的向量。您不能真正地突变SVector
,但是可以使用方便的语法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