Parametric type: type stable getfield

Hi everyone,
I have a bit of trouble understanding what is going on and why I’m getting type instability (julia 0.7).

I create a parametric type of the form

mutable struct Point{T1<:Real}
    x::T1
    y::Int64
end

Now this is type stable:

p = Point(1,1)
@code_warntype p.x

while this isn’t:

p = Point(1.5,1)
@code_warntype p.x

In particular, why do I get that Union? I’m just getting started on 0.7 and, if I understood correctly I don’t have to worry too much about this union. Still, i’d like to understand what exactly is going on.

Thanks a lot :slight_smile:

Put things in functions for optimizations to properly kick in.

julia> f(p) = p.x
f (generic function with 1 method)

julia> @code_warntype f(p)
Body::Float64
1 1 ─ %1 = (Base.getfield)(p, :x)::Float64                                                                                   │╻ getproperty
  └──      return %1     

The reason it infers when all fields are the same type is, well, all fields are the same type.

2 Likes

Cool, but why does that not work if not in a function? Should’t the compiler know the type of p.x anyway?

p.x is the same as getproperty(p, :x) so what happens in your case is similar to:

julia> f(p, s) = getproperty(p, s)
f (generic function with 2 methods)

julia> @code_warntype f(p, :x)
Body::Union{Float64, Int64}
1 1 ─ %1 = (Base.getfield)(p, s)::Union{Float64, Int64}                                                                      │╻ getproperty
  └──      return %1                                                                                                         │

In other words, the .x is not considered a constant from the way you use the macro.

In fact, we can see what the macro expands to:

julia> @macroexpand @code_warntype p.x
:((InteractiveUtils.code_warntype)(getproperty, (Base.typesof)(p, :x)))

and

julia> (Base.typesof)(p, :x)
Tuple{Point{Float64},Symbol}

so we end up calling

julia> code_warntype(getproperty, Tuple{Point{Float64},Symbol})
Body::Union{Float64, Int64}
18 1 ─ %1 = (Base.getfield)(x, f)::Union{Float64, Int64}                                                                                  │
   └──      return %1    

where it is clear that the “constness” of .x is lost.

2 Likes

Great, that explains it. Thanks a lot.

I also encounter a similar problem if I want to define my own “get” for a 2 layered nested structure. My question is does this kind of code warn type has an implication for performance? In other word, it is fine not make my own get function type stable.

It can have an impact on performance, yes. Regarding your specific problem, a bit more information would help.

Hi, here is what I am trying to do

abstract type AbstractParticleProperties end

mutable struct Particle{N, S, T}
   properties::ParticleProperties{N, T}
   states::ParticleStates{S, T}
end

mutable struct ParticleProperties{N <:Int, T <:Real}  <:AbstractParticleProperties
   id::N
   mass::T  
  #More fields ...
end

mutable struct ParticleStates{S <:Int, T <:Real} 
   x::SVector{S, T}
   f::SVector{S, T}
 #More fields...
end

In my code I typically need to access kind or id of a particle (an instance of Particle). Originally, my desire was that I don’t want to always write particle.properties.(some field in particle properties). So I implement sth like

function get_properties_field(particle::Particle{N, S, T}, s::Symbol) where {N, S, T}
    getfield(getfield(particle, properties), s)
end

which I discovered that it is not typed stable. After reading what you have described, I implements sth like this instead

function id(particle::Particle{N, S, T}) where {N, S, T}
    particle.properties.id
end

When I measure the performance, it turns out that the second version performance a lot better :slight_smile: . However, this means that I have to write sth like this for every fields in the ParticleProperties. Moreover, every time that I want
create a subtype of AbstractParticleProperties with a different field, says electric charge. I have to implement sth like above again. Is there a nice way work around this? Alternatively, I don’t have to think about this problem just use p.properties.(whatever). Hopefully, this clarify up my question above.