Efficiently create large array of structs without allocations

I want to create a very large array of a custom struct type. However, I am finding that the loop that fills the (pre-allocated array) is requiring a large amount of memory allocations and is very slow as a result.

I assume this is because when I set i’th array entry to MyStruct(), I am first creating MyStruct, assigning it (copying), and then immediately throwing it away. How do I avoid this?

This is a working toy code example showing the problem:

# Example code

using StaticArrays

struct MyStruct
    field1::SVector{4, Float32}
    field2::SMatrix{2, 2, Float32}
end

function myfill(arrayOfStructs)
    for i in eachindex(arrayOfStructs)
        arrayOfStructs[i] = MyStruct(
            SVector{4, Float32}([i, i, i, i]),
            SMatrix{2, 2, Float32}([i i; i i])
        )
    end
end

arrayOfStructs = Array{MyStruct}(undef, 10_000_000)

@time myfill(arrayOfStructs)

You want to use the macro @SVector. Without it, you are allocating a vanilla Julia array before converting to SVector. See docs here.

1 Like

That’s a good catch, but I’ve oversimplified my code too much. Here’s a better example, were I’m trying to copy portions of arrays into a new array-of-structs structure.

Ideally, I would like the call to myfill() to be non-allocating, since I have already allocated space for the array. I am assuming the allocations now arise from the use of the @views?

# Example code

using BenchmarkTools
using StaticArrays

struct MyStruct
    field1::SVector{4, Float32}
    field2::SMatrix{2, 2, Float32}
end

function myfill(arrayOfStructs, origin4, origin2x2)
    for i in eachindex(arrayOfStructs)
        arrayOfStructs[i] = MyStruct(
            SVector{4, Float32}(@view origin4[i, :]),
            SMatrix{2, 2, Float32}(@view origin2x2[i, :, :])
        )
    end
end

N = 1_000_000

origin4 = rand(Float32, N, 4)
origin2x2 = rand(Float32, N, 2, 2)
arrayOfStructs = Array{MyStruct}(undef, N)

@benchmark myfill($arrayOfStructs, $origin4, $origin2x2) samples=1 evals=1 seconds=20

MyStruct is incompletely parameterized.

julia> struct MyStruct
           field1::SVector{4, Float32}
           field2::SMatrix{2, 2, Float32, 4} # you need to specify the length too as the fourth parameter
       end

julia> @btime myfill($arrayOfStructs, $origin4, $origin2x2);
  2.484 μs (0 allocations: 0 bytes)
5 Likes

Thank you! That is exactly it! :slight_smile:

:ok_hand: you may want to mark jishnub’s answer as “solution”.

1 Like