Strange allocation

I’m writing a type to represent an affine transformation (y=Ax+b), using static arrays. Here’s the code

using BenchmarkTools
using StaticArrays

struct Affine{F, T, R}
    matrix :: SMatrix{T, F, R}
    vector :: SVector{T, R}
end

function mkaffine(::Val{T}) where T
    mx = zero(SMatrix{T,T,Int})
    vec = zero(SVector{T,Int})
    Affine(mx, vec)    # option 1
    # (mx, vec)          # option 2
end

function test()
    @btime mkaffine(Val(3))
end

test()

The mkaffine() function has been simplified from my actual use case. The Affine constructor is type stable, the struct has no fields with non-concrete types, everything involved is immutable and should be able to live fully on the stack. Yet I get allocations. Why?

If you comment out Affine(mx, vec) and return just (mx, vec) instead, the allocations go away. The two types should have exactly the same memory layout though.

Variant one:

Body::Affine{3,3,Int64}
1 ─ %1 = %new(Affine{3,3,Int64}, $(QuoteNode([0 0 0; 0 0 0; 0 0 0])), $(QuoteNode([0, 0, 0])))::Affine{3,3,Int64}
└──      return %1

Variant 2:

Body::Tuple{SArray{Tuple{3,3},Int64,2,9},SArray{Tuple{3},Int64,1,3}}
1 ─     return ([0 0 0; 0 0 0; 0 0 0], [0, 0, 0])

You need to specify all parameters to SMatrix.

https://github.com/JuliaArrays/StaticArrays.jl/blob/ee01ea10abcf455c84c8a01de971fa3c09a8bd53/src/SMatrix.jl#L2-L3

Note 4 parameters.

For example:

julia> aff = mkaffine(Val(3));

julia> Ts = fieldtypes(typeof(aff))
(SArray{Tuple{3,3},Int64,2,L} where L, SArray{Tuple{3},Int64,1,3})

julia> isconcretetype.(Ts)
(false, true)

The L is not specified.

1 Like

Of course, thank you. Would love it if basic arithmetic was possible with type variables so that we wouldn’t have to drag the length around.