Runtime creation of SArray

I have a program where small arrays are involved where the size need to change at some point at runtime depending on some computations. Nevertheless, I wanted to use StaticArrays since there is some linear algebra involved in the end which way faster with SArrays in that size.

Since the length is a required parameter for SArrays I came across Val types to reduce the cost of runtime creation of the SArrays

using StaticArrays
using BenchmarkTools

function make_SVector_1(a) 
    return SVector{length(a)}(a)
end

function make_fixed_length_SV(a,::Val{N}) where N
    return SVector{N}(a)
end

function make_SVector_2(a) #just a convenience wrapper
    return make_fixed_length_SV(a,Val{length(a)}())
end


a=rand(20)

@btime make_SVector_1(a)
 1.059 μs (10 allocations: 752 bytes)
 
@btime make_SVector_2(a)
 165.854 ns (1 allocation: 176 bytes)

So timewise the second variant I can tolerate in the code.
Is there any reason to not do this or is there a better way to create an SArray at runtime?

Probably you should stick to the default constructors available, @SVector(a). But the most important for performance is to keep function barriers that preserve type stability of the foregoing computations after that size change.

julia> f(a) = @SVector [a[i] for i in 1:length(a)]
f (generic function with 1 method)

julia> @btime f($a)
  8.286 ns (0 allocations: 0 bytes)

edit: In the example above I have been fooled by the fact that a is defined when the macro @SVector is parsed, thus the length of it is known at compile time. You cannot do that dynamically with the same performance.

If you know exactly the initial and final sizes and values, you can try something more sophisticated, like this:

julia> a = rand(SVector{3,Float64})

julia> f(a::SVector{N,T},x) where {N,T} = SVector{N+1,T}(ntuple(i -> i <= N ? a[i] : x, N+1))
f (generic function with 2 methods)

julia> f(a,1.0)
4-element SVector{4, Float64} with indices SOneTo(4):
 0.08759991208199902
 0.38144466002582855
 0.658066648655117
 1.0

julia> @btime f(x,1.0) setup=(x=rand(SVector{3,Float64})) evals=1
  23.000 ns (0 allocations: 0 bytes)
4-element SVector{4, Float64} with indices SOneTo(4):
 0.6213191707449999
 0.1885468618825823
 0.07677713807317965
 1.0



2 Likes

The constructor was exactly my issue. So when the size is known at compile time that works well, but fastest runtime creation I could only get via this workaround which is to my understanding basically like you suggested a function barrier.
So currently I have just these little helper functions from my first post in my code for creating SVectors from any Vector on the fly. But maybe just have this as a constructor of an SVector like SVector(a) would be nice.
For sure it would be better to not have to do runtime creation of any SArray at all.

regarding extending a SVector, that works already pretty nice like

@btime [c;1] setup=c=SVector{3}(1.0,2.0,3.0) evals=1
20.000 ns (0 allocations: 0 bytes)
4-element SVector{4, Float64} with indices SOneTo(4):
 1.0
 2.0
 3.0
 1.0

which is probably implemented similar to what you suggested, but I haven’t checked.
I did not know about the evals argument from @btime but it helps a lot with the timing of SArray things. Thanks :slight_smile: