简体   繁体   中英

Updating a static array, in a nested function without making a temporary array? <Julia>

I have been banging my head against a wall trying to use static arrays in julia.

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

They are fast but updating them is a pain. This is no surprise, they are meant to be immutable!

But it is continuously recommended to me that I use static arrays even though I have to update them. In my case, the static arrays are small, just length 3, and i have a vector of them, but I only update 1 length three SVector at a time.


Option 1

There is a really neat package called Setfield that allows you to do inplace updates of SVectors in Julia.

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

The catch... it updates the local copy. So if you are in a nested function, it updates the local copy. So it comes with some book keeping since you have to inplace update the local copy and then return that copy and update the actual array of interest. You can't pass in your desired array and update it in place, at least, not that I can figure out! Now, I do not mind bookeeping, but I feel like updating a local copy, then returning the value, updating another local copy, and then returning the values and finally updating the actual array must come with a speed penalty. I could be wrong.


Option 2

It bugs me that in order to do an update a static array I must

exampleSVector::SVector{3,Float64} <-- just to make clear its type and size

exampleSVector = [value1, value2, value3]

This will update the desired array even if it is inside a function, which is nice and the goal, but if you do this inside a function it creates a temporary array. And this kills me because my function is in a loop that gets called 4+ million times, so this creates a ton of allocations and slows things down.


How do I update an SVector for the Option 2 scenario without creating a temporary array?

For the Option 1 scenario, can I update the actual array of interest rather than the local copy?

If this requires a simple example code, please say so in the comments, and I will make one. My thinking is that it is answerable without one, but I will make one if it is needed.

EDIT:

MCVE code - Option 1 works, option 2 does not.

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)

There are two types of static arrays - mutable (starting with M in type name) and immutable ones (starting with S ) - just use the mutable ones! have a look at the example below:

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.

Let us see some simple benchmark (ordinary array, vs mutable static vs immutable static):

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

I hope that I have understood your question correctly. It's a bit hard with a MWE like this, that does a lot of things that are mostly redundant and a bit confusing.

There seems to be two alternative interpretations here: Either you really need to update ('mutate') an SVector , but your MWE fails to demonstrate why. Or, you have convinced yourself that you need to mutate, but you actually don't.

I have decided to focus on alternative 2: You don't really need to 'mutate'. Rewriting your code from that point of view simplifies it greatly.

I couldn't find any reason for you to mutate any static vectors here, so I just removed that. The behaviour of the INNER! function with the inputs was very confusing. You provide two inputs but don't use either of them, so I removed those inputs.

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)

This runs fast and with zero allocations. In general with StaticArrays, don't try to mutate things, just create new instances.

Even though it's not clear from your MWE, there may be some legitimate reason why you may want to 'mutate' an SVector . In that case you can use the setindex method of StaticArrays, you don't need 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

To clarify: setindex (without a ! ) does not mutate its input, but creates a new instance with one index value changed.

If you really do need to 'mutate', perhaps you can make a new MWE that shows this. I would recommend that you try to simplify it a bit, because it is quite confusing now. For example, the inclusion of the type Keep seems entirely unnecessary and distracting. Just make a Vector of SVector s and show what you want to do with that.

Edit: Here's an attempt based on the comments below. As far as I understand it now, the question is about modifying a vector of SVector s. You cannot really mutate the SVector s, but you can replace them using a convenient syntax, setindex , where you can keep some of the elements and change some of the others:

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

Now we replace the second element of each element of oldvec with the corresponding one in replacevec . First a one-liner:

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

Then an even faster one with a loop:

for i in eachindex(oldvec, replacevec)
    @inbounds oldvec[i] = setindex(oldvec[i], replacevec[i][2], 2)
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM