Type instability of getfield()

See the following example

julia> struct Bar{T<:Real}
       x::T
       end

julia> struct Foo{T <: Real}
       bars::Array{Bar{T},1}
       end

julia> foo = Foo(fill(Bar(rand()),2))
Foo{Float64}(Bar{Float64}[Bar{Float64}(0.06299965346289693), Bar{Float64}(0.06299965346289693)])

julia> getfield.(foo.bars, :x)
2-element Array{Float64,1}:
 0.06299965346289693
 0.06299965346289693

julia> f(x::Int) = getfield(foo.bars[1], :x)
f (generic function with 3 methods)

julia> @code_warntype f(1)
Variables
  #self#::Core.Compiler.Const(f, false)
  x::Int64

Body::Any
1 ─ %1 = Base.getproperty(Main.foo, :bars)::Any
│   %2 = Base.getindex(%1, 1)::Any
│   %3 = Main.getfield(%2, :x)::Any
└──      return %3

Seems to be correctly typed, I don’t see why getfield is not type-stable.

The reason I’m using getfield is because in some cases I want to do getfield.(foo.bars, :x) which would return an Array{Float64,1}.

I think you’re seeing that type instability because foo is global, and f closes over that global variable, so between two calls of f the complier doesn’t know that foo hasn’t changed into some other type.

you may want to pass foo as an argument to f, or create foo inside a function and return the closure f.

2 Likes

Ignoring the global variable already mentioned,

f(x::Int) = getfield(foo.bars[1], :x)

doesn’t make sense to me. You are not using the input argument x?

1 Like

It was only for the example. In practice I wanted to use the broadcasting f() = getfield.(foo.bars, :x).
While trying to find the source of instability it boiled down to the non-broadcasted call.

I have to look again because I do see type instability in my code but there it is not global scope.

If the variable that f is closing over has some sufficiently-complicated stuff going on with it, it’s possible that the compiler can’t prove that it’ll never change type so it’s doing the dynamic dispatch you’re seeing.

You could try putting a let foo=foo block around your f closure definition to see if that helps.

Actually, I simply used [bar.x for bar in foo.bars] instead of the broadcast. It’s old code that I’m optimizing so I think didn’t know this notation back then.

But thank you this has been instructive.