# Type instability with static matrices when full size not given?

**URL:** <https://discourse.julialang.org/t/type-instability-with-static-matrices-when-full-size-not-given/63079>\
**Category:** Performance\
**Tags:** type-stability, staticarrays\
**Created:** [June 17, 2021, 11:49am UTC](https://discourse.julialang.org/t/type-instability-with-static-matrices-when-full-size-not-given/63079 "2021-06-17T11:49:51Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![nabla](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nabla/32/26089_2.png) [@nabla](https://discourse.julialang.org/u/nabla)\
**Post date:** [June 17, 2021, 11:49am UTC](https://discourse.julialang.org/t/type-instability-with-static-matrices-when-full-size-not-given/63079/1 "2021-06-17T11:49:51Z")

</div>

I ran across the following, very wierd situation: if I don’t specify the 4th parameter in a `SMatrix` (which is supposed to be computed as the product of the sizes, then the code slows down considerably. MWE:

```julia
using BenchmarkTools
using StaticArrays

Good = SMatrix{1,1,Int,1}
Bad = SMatrix{1,1,Int}

struct Container{T} x::T end
fetch(c::Container{T}) where T = c.x

let
    list = [Good([i]) for i=1:1000]
    global goodprods(x) = [fetch(x)*y for y∈list]
end

let
    list = [Bad([i]) for i=1:1000]
    global badprods(x) = [fetch(x)*y for y∈list]
end

```

Then  
`@benchmark goodprods(m) setup = (m = Container{Good}(Good([1])))` gives 1 allocation, median time 1.236 μs, while  
`@benchmark badprods(m) setup = (m = Container{Bad}(Bad([1])))` gives 2001 allocations, median time 31.831 μs.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [June 17, 2021, 12:15pm UTC](https://discourse.julialang.org/t/type-instability-with-static-matrices-when-full-size-not-given/63079/2 "2021-06-17T12:15:19Z")

</div>

Your `Container` has an non-concretely typed field, since you force it to be non-concretely typed with the incompletely typed `Bad`. Inference thus can’t assume more about the item, even though the actual object has all type parameters set (as all objects at runtime do). You can check this by using functions that return functions instead of your `let`s - only those passed in with `Container{Bad}` are type unstable, even if they access “Bad” lists.

```julia
julia> typeof(m_good)             
Container{SMatrix{1, 1, Int64, 1}}
                                  
julia> fieldtypes(Container{Good})
(SMatrix{1, 1, Int64, 1},)        
                                  
julia> typeof(m_bad)              
Container{SMatrix{1, 1, Int64}}   
                                  
julia> fieldtypes(Container{Bad}) 
(SMatrix{1, 1, Int64},)           

```

> **Click for expanded @code\_warntype info.**
>
> ```julia
> julia> function good_gen()                                                                                                        
> list = [Good([i]) for i in 1:1000 ]                                                                                   
> x -> [fetch(x)*y for y ∈ list]                                                                                       
> end                                                                                                                        
> good_gen (generic function with 2 methods)                                                                                        
>                                                                                                                                   
> julia> function bad_gen()                                                                                                         
> list = [Bad([i]) for i in 1:1000 ]                                                                                    
> x -> [fetch(x)*y for y ∈ list]                                                                                       
> end                                                                                                                        
> bad_gen (generic function with 2 methods)                                                                                         
>                                                                                                                                   
> julia> good_prod = good_gen()                                                                                                     
> #34 (generic function with 1 method)                                                                                              
>                                                                                                                                   
> julia> bad_prod = bad_gen()                                                                                                       
> #40 (generic function with 1 method)                                                                                              
>                                                                                                                                   
> julia> m_bad = Container{Bad}(Bad([1]))                                                                                           
> Container{SMatrix{1, 1, Int64}}([1])                                                                                              
>                                                                                                                                   
> julia> m_good = Container{Good}(Good([1]))                                                                                        
> Container{SMatrix{1, 1, Int64, 1}}([1])                                                                                           
>                                                                                                                                   
> julia> @code_warntype good_prod(m_good)                                                                                           
> MethodInstance for (::var"#34#37"{Vector{SMatrix{1, 1, Int64, 1}}})(::Container{SMatrix{1, 1, Int64, 1}})                         
> from (::var"#34#37")(x) in Main at REPL[35]:3                                                                                   
> Arguments                                                                                                                         
> #self#::var"#34#37"{Vector{SMatrix{1, 1, Int64, 1}}}                                                                            
> x::Container{SMatrix{1, 1, Int64, 1}}                                                                                           
> Locals                                                                                                                            
> #35::var"#35#38"{Container{SMatrix{1, 1, Int64, 1}}}                                                                            
> Body::Vector{SMatrix{1, 1, Int64, 1}}                                                                                             
> 1 ─ %1 = Main.:(var"#35#38")::Core.Const(var"#35#38")                                                                             
> │ %2 = Core.typeof(x)::Core.Const(Container{SMatrix{1, 1, Int64, 1}})                                                           
> │ %3 = Core.apply_type(%1, %2)::Core.Const(var"#35#38"{Container{SMatrix{1, 1, Int64, 1}}})                                     
> │ (#35 = %new(%3, x))                                                                                                      
> │ %5 = #35::var"#35#38"{Container{SMatrix{1, 1, Int64, 1}}}                                                                     
> │ %6 = Core.getfield(#self#, :list)::Vector{SMatrix{1, 1, Int64, 1}}                                                            
> │ %7 = Base.Generator(%5, %6)::Base.Generator{Vector{SMatrix{1, 1, Int64, 1}}, var"#35#38"{Container{SMatrix{1, 1, Int64, 1}}}} 
> │ %8 = Base.collect(%7)::Vector{SMatrix{1, 1, Int64, 1}}                                                                        
> └── return %8                                                                                                                
>                                                                                                                                   
>                                                                                                                                   
> julia> @code_warntype good_prod(m_bad)                                                                                            
> MethodInstance for (::var"#34#37"{Vector{SMatrix{1, 1, Int64, 1}}})(::Container{SMatrix{1, 1, Int64}})                            
> from (::var"#34#37")(x) in Main at REPL[35]:3                                                                                   
> Arguments                                                                                                                         
> #self#::var"#34#37"{Vector{SMatrix{1, 1, Int64, 1}}}                                                                            
> x::Container{SMatrix{1, 1, Int64}}                                                                                              
> Locals                                                                                                                            
> #35::var"#35#38"{Container{SMatrix{1, 1, Int64}}}                                                                               
> Body::Vector                                                                                                                      
> 1 ─ %1 = Main.:(var"#35#38")::Core.Const(var"#35#38")                                                                             
> │ %2 = Core.typeof(x)::Core.Const(Container{SMatrix{1, 1, Int64}})                                                              
> │ %3 = Core.apply_type(%1, %2)::Core.Const(var"#35#38"{Container{SMatrix{1, 1, Int64}}})                                        
> │ (#35 = %new(%3, x))                                                                                                      
> │ %5 = #35::var"#35#38"{Container{SMatrix{1, 1, Int64}}}                                                                        
> │ %6 = Core.getfield(#self#, :list)::Vector{SMatrix{1, 1, Int64, 1}}                                                            
> │ %7 = Base.Generator(%5, %6)::Base.Generator{Vector{SMatrix{1, 1, Int64, 1}}, var"#35#38"{Container{SMatrix{1, 1, Int64}}}}    
> │ %8 = Base.collect(%7)::Vector                                                                                                 
> └── return %8                                                                                                                
>                                                                                                                                   
>                                                                                                                                   
> julia> @code_warntype bad_prod(m_good)                                                                                            
> MethodInstance for (::var"#40#43"{Vector{SMatrix{1, 1, Int64, 1}}})(::Container{SMatrix{1, 1, Int64, 1}})                         
> from (::var"#40#43")(x) in Main at REPL[36]:3                                                                                   
> Arguments                                                                                                                         
> #self#::var"#40#43"{Vector{SMatrix{1, 1, Int64, 1}}}                                                                            
> x::Container{SMatrix{1, 1, Int64, 1}}                                                                                           
> Locals                                                                                                                            
> #41::var"#41#44"{Container{SMatrix{1, 1, Int64, 1}}}                                                                            
> Body::Vector{SMatrix{1, 1, Int64, 1}}                                                                                             
> 1 ─ %1 = Main.:(var"#41#44")::Core.Const(var"#41#44")                                                                             
> │ %2 = Core.typeof(x)::Core.Const(Container{SMatrix{1, 1, Int64, 1}})                                                           
> │ %3 = Core.apply_type(%1, %2)::Core.Const(var"#41#44"{Container{SMatrix{1, 1, Int64, 1}}})                                     
> │ (#41 = %new(%3, x))                                                                                                      
> │ %5 = #41::var"#41#44"{Container{SMatrix{1, 1, Int64, 1}}}                                                                     
> │ %6 = Core.getfield(#self#, :list)::Vector{SMatrix{1, 1, Int64, 1}}                                                            
> │ %7 = Base.Generator(%5, %6)::Base.Generator{Vector{SMatrix{1, 1, Int64, 1}}, var"#41#44"{Container{SMatrix{1, 1, Int64, 1}}}} 
> │ %8 = Base.collect(%7)::Vector{SMatrix{1, 1, Int64, 1}}                                                                        
> └── return %8                                                                                                                
>                                                                                                                                   
>                                                                                                                                   
> julia> @code_warntype bad_prod(m_bad)                                                                                             
> MethodInstance for (::var"#40#43"{Vector{SMatrix{1, 1, Int64, 1}}})(::Container{SMatrix{1, 1, Int64}})                            
> from (::var"#40#43")(x) in Main at REPL[36]:3                                                                                   
> Arguments                                                                                                                         
> #self#::var"#40#43"{Vector{SMatrix{1, 1, Int64, 1}}}                                                                            
> x::Container{SMatrix{1, 1, Int64}}                                                                                              
> Locals                                                                                                                            
> #41::var"#41#44"{Container{SMatrix{1, 1, Int64}}}                                                                               
> Body::Vector                                                                                                                      
> 1 ─ %1 = Main.:(var"#41#44")::Core.Const(var"#41#44")                                                                             
> │ %2 = Core.typeof(x)::Core.Const(Container{SMatrix{1, 1, Int64}})                                                              
> │ %3 = Core.apply_type(%1, %2)::Core.Const(var"#41#44"{Container{SMatrix{1, 1, Int64}}})                                        
> │ (#41 = %new(%3, x))                                                                                                      
> │ %5 = #41::var"#41#44"{Container{SMatrix{1, 1, Int64}}}                                                                        
> │ %6 = Core.getfield(#self#, :list)::Vector{SMatrix{1, 1, Int64, 1}}                                                            
> │ %7 = Base.Generator(%5, %6)::Base.Generator{Vector{SMatrix{1, 1, Int64, 1}}, var"#41#44"{Container{SMatrix{1, 1, Int64}}}}    
> │ %8 = Base.collect(%7)::Vector                                                                                                 
> └── return %8                                                                                                                
> 
> ```

I think those kinds of incompletely typed type parameters should at least be colored differently in `@code_warntype` though, that would make the issue more apparent. Do you mind opening an issue about this?

---

<div class="post-metadata">

**Author:** ![nabla](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nabla/32/26089_2.png) [@nabla](https://discourse.julialang.org/u/nabla)\
**Post date:** [June 17, 2021, 12:27pm UTC](https://discourse.julialang.org/t/type-instability-with-static-matrices-when-full-size-not-given/63079/3 "2021-06-17T12:27:58Z")

</div>

Thanks! So the culprit is spotted in the very last line, `%8 = Base.collect(%7)::Vector` ? Indeed I had tried to use `@code_warn` but couldn’t make anything of its output.

I’m still troubled by the fact that StaticArrays documentation says that the last argument is computed automatically, while it seems that as soon as StaticArrays are used inside another structure then the last argument should by supplied explicitly, under penalty of huge slowdown. Do I understand this correctly? Is this a bug in StaticArrays, or just something that should be documented to warn poor souls like me in the future?

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [June 17, 2021, 12:34pm UTC](https://discourse.julialang.org/t/type-instability-with-static-matrices-when-full-size-not-given/63079/4 "2021-06-17T12:34:25Z")

</div>

> [@nabla](#):
>
> I’m still troubled by the fact that StaticArrays documentation says that the last argument is computed automatically

You’re misunderstanding things:

```julia
julia> list_bad = [Bad([i]) for i=1:1000];

julia> eltype(list_bad) == Good
true                                                               

```

The last parameter _is_ calculated correctly, even for just `Bad`:

```julia
julia> Bad([1]) |> typeof                                           
SMatrix{1, 1, Int64, 1} (alias for SArray{Tuple{1, 1}, Int64, 2, 1})

```

It’s just that in `Container{Bad}`, you _force_ the type of its only field to be non-concrete, even if the element saved there is completely specified:

```julia
julia> ex1 = Container{Bad}(Bad([1]))                               
Container{SMatrix{1, 1, Int64}}([1])                                
                                                                    
julia> fieldtypes(typeof(ex1))                                      
(SMatrix{1, 1, Int64},)                                             
                                                                    
julia> typeof(ex1.x)                                                
SMatrix{1, 1, Int64, 1} (alias for SArray{Tuple{1, 1}, Int64, 2, 1})

```

Since inference relies on the fieldtype to check which type to propagate, it fails and just gives you `Vector`. After all, at compile time it can’t know that there will be an element in there with a tighter specification than what you explicitly request with `Container{Bad}`. Additionally, inference is not aware of the special invariant imposed by the first two type parameters on the last one, so it can’t assume that it will be concretely typed anyway.

E.g. with `Container{Bad}` it’s the same as if you had written it like this:

```julia
struct BadContainer
   x::SMatrix{1,1,Int64} # non-concrete type.
end

```

> [@nabla](#):
>
> Is this a bug in StaticArrays, or just something that should be documented to warn poor souls like me in the future?

All this is generally documented, in the form of “avoid abstract/non-concrete fields”.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [June 17, 2021, 12:45pm UTC](https://discourse.julialang.org/t/type-instability-with-static-matrices-when-full-size-not-given/63079/5 "2021-06-17T12:45:20Z")

</div>

I have [opened an issue](https://github.com/JuliaLang/julia/issues/41251) about the printing in `@code_warntype`.

Note that this doesn’t have anything to do with the semantics at play here - those are working as intended, as far as I can tell.
