How to initialize an empty StaticArray?

For performance reasons, I want to replace usage of standard arrays with StaticArrays. However, I couldn’t find how to initialize an empty StaticArray of a given size, that I can populate as values become available. I am looking for something equivalent of:

Array{Int64}(undef, 10)

but for StaticArrays. Is this possible?

Thank you.

Theoretically yes, as I learned from Create a struct with uninitialized fields. But practically: I don’t know.

since staticarray is static, if you created an empty one, you couldn’t ever put stuff in it.

1 Like

I see. But you still could replace it in nested container;)

Static arrays are like numbers. You cannot create an empty number, and if you have a vector of numbers, when you want to change a value you replace the number at a position in the vector by another number.

The same goes for static arrays. They are not mutable, you have to create them with values and replace them when needed. This is advantageous when the arrays are small.

1 Like

Thank you for the explanation. I misunderstood them. I thought that there size could not be changed, however, the values in them could be changed. For example, the following code works, and does not throw any errors.

function arr3(len)
	a = Array{Int}(undef, len)
	a = StaticArrays.SizedVector{len}(a)
	for i = 1:len
		a[i] = i
	end
	for j = 1:1000
		a[rand(1:len)]
	end
end
1 Like

Oh yes, StaticArrays also provides mutable, statically sized arrays. For those the explanation above does not apply. (But the performance is not the same either).

The interface for mutable, statically sized, arrays in StaticArrays is the MArray. (I never saw the use of that constructor you used there before, I guess it is an internal).

That’s the kind of example I love: using a dynamic array to initialize a possibly static array;)

Thank you! For these statically sized arrays, is the performance better than the regular arrays? And, if yes, can these by initialized with undef or something similar?

The constructor I used is documented here:
https://juliaarrays.github.io/StaticArrays.jl/stable/pages/api/#StaticArrays.SizedArray

Thanks again.

I am not suggesting that that is a good way :slight_smile:

But I did try to see if that helped over just using the dynamic array, and it did not. It made performance much worse. Hence, my question.

1 Like
julia> using StaticArrays

julia> MVector{10,Float64}(undef)
10-element MVector{10, Float64} with indices SOneTo(10):
 4.4e-323
 6.9288606158516e-310
 6.92886061148325e-310
 0.0
 6.9288606158516e-310
 6.92886061148325e-310
 0.0
 6.9288606158516e-310
 6.9288723779309e-310
 0.0
2 Likes

Maybe this can help you understand where these static arrays can be useful: Immutable variables · JuliaNotes.jl

Thank you, Elrod for the solution.

Thank you also [leandromartinez98] for the link. I will read it. On my computer, the MVector works slower than a regular Array. I have included a small example I tried. But the link you have will probably shed light on that.

function arr2(len)
	a = Array{Int}(undef, len)
	for i = 1:len
		a[i] = i
	end
	for j = 1:1000
		a[rand(1:len)]
	end
end

function arr3(len)
	a = MVector{len, Int}(undef)
	for i = 1:len
		a[i] = i
	end
	for j = 1:1000
		a[rand(1:len)]
	end
end
julia> @btime arr2(100)
  6.408 μs (1 allocation: 896 bytes)

julia> @btime arr3(100)
  20.429 μs (10 allocations: 1.36 KiB)

The length of a static array is part of its type. Thus, passing len as an Int results in dynamic dispatches: the only thing known at compile time is that len is an Int.
If you instead pass it as a ::Val{len}, that is an object of type Val{len}, then the value is known at compile time.

julia> function arr3(::Val{len}) where {len}
           a = MVector{len, Int}(undef)
           for i = 1:len
               @inbounds a[i] = i
           end
           for j = 1:1000
               @inbounds a[rand(1:len)]
           end
       end
arr3 (generic function with 2 methods)

julia> @btime arr2(100)
  3.553 μs (1 allocation: 896 bytes)

julia> @btime arr3(100)
  18.291 μs (10 allocations: 1.30 KiB)

julia> @btime arr3(Val(100))
  3.129 μs (0 allocations: 0 bytes)

Note also the 0 allocations.
If you want to actually return a from the function, call SVector(a) to convert it into an SVector before returning to still get 0 allocations.

2 Likes

Thanks very much for the explanation. I did not know anything about Val. Will read up on that.

Although the suggestion above is of course valid, I think it is not a common pattern (please Elrod correct me if I’m wrong), because:

  1. That function specializes for every len. Thus, if the lengths of your arrays are changing frequently in your code, that will cause dynamic dispatch to kick in (every time you call the function with a different len it will call a different method of the function). In these cases using static arrays may not be worthwhile anyway.

  2. Most commonly the length of the array you want to create can be deduced from some of the input parameters of your function. For example, this is more common:

julia> f(x::T) where T = x + rand(T)
f (generic function with 1 method)

julia> x = SVector{3,Float64}(1,1,1)
3-element SVector{3, Float64} with indices SOneTo(3):
 1.0
 1.0
 1.0

julia> @btime f($x)
  10.620 ns (0 allocations: 0 bytes)
3-element SVector{3, Float64} with indices SOneTo(3):
 1.9554100262060785
 1.1736294084146963
 1.5112265205467528

You don’t need to specify explicitly the size of the random vector that is being created inside, because you know that it has to be of the same size as x. And if x is a static array, the random vector will have the same type.

As you see, in the example above there is no “uninitialized” array, the rand(T) creates the random vector, which is added to x. There are may ways (which do not solve all problems, but many of them), to build these arrays, for example, using ntuples:

julia> g(x::SVector{N,T}) where {N,T} = x + SVector{N,T}(ntuple(i -> 2*i, N))
g (generic function with 1 method)

julia> x = SVector{3,Int}(1,1,1)
3-element SVector{3, Int64} with indices SOneTo(3):
 1
 1
 1

julia> @btime g($x)
  0.022 ns (0 allocations: 0 bytes)
3-element SVector{3, Int64} with indices SOneTo(3):
 3
 5
 7


Here x is being added to a static vector of the same type, but which is constructed from a n-tuple. Since N is the length of the ntuple and is a parameter of the type of x, it is known at compile time as well, and the function specializes to it.

With the do syntax you can build pretty complicated arrays with that:

julia> function h(x::SVector{N,T}) where {N,T}
           y = ntuple(N) do i
                   if i == 1 
                       zero(T)
                   else
                       10*i*rand(T)
                   end
               end
           return x + SVector{N,T}(y)
       end
h (generic function with 1 method)

julia> x = zeros(SVector{5,Float64});

julia> @btime h($x)
  21.608 ns (0 allocations: 0 bytes)
5-element SVector{5, Float64} with indices SOneTo(5):
  0.0
  2.4393119543628927
  8.108974913748623
 37.62484099830813
 13.538314396037276


3 Likes