Type stability in hierarchichal function calls

Hi all,

I have a question on how type stability traverses into layers of function calls. Meaning, if I have a function assembly that is not completely type stable, because it contains a Vector{Types}. In assembly I call another function _local_assembly, which is type stable. When I call @code_warntype I can only see one layer deep, so I can check the type stabilities independently, but not see if _local_assembly is actually type stable in the assembly call.

function _local_assembly(::Type{T}) where {T}
  ... # do something computationally intensive
end

function assembly(a::Vector{Types})
  for t in eachindex(a)
    _local_assembly(a[t])
  end
end

In this case local assembly can specialise per type, but assembly is type-unstable. Let’s say that the heavy lifting is done in _local_assembly, and assembly just dispatches the computation per type. Can I assume that the _local_assembly function is actually type stable, and performant, or does the type-instability “follow” the function calls?

Regarding the underlying question about debugging such things: Cthulhu.jl provides the @descend macro which provides similar infos as @code_warntype and much more, plus it allows you to interactively “descend” deeper into nested function calls and inspect what goes on at each level.

In general, the pattern you have above looks like a “function barrier”, i.e. the inner function _local_assembly will be called with each particular element of the Vector{Type} and, as you said, a specialized method will be compiled for each case (inside the inner function, the object then has a known type and there is no type instability).

There will still be “dynamic dispatch” in general since inside assembly the runtime has to figure out which method of _local_assembly to call, but if the operation within _local_assembly takes much longer than the outer loop over the Vector{Type}, there should be no big impact in the end. Only when you have a “hot loop”, i.e. you run over a large abstract collection (like Vector{Type}) and do many small operations (i.e. a is very long and _local_assembly is fast), you will notice the large overhead of dynamic/runtime dispatch.

1 Like

The previous poster described function barriers well. But just in case, the manual section on function barriers is here.

1 Like