I am working on a library where I cannot avoid heterogeneous collections, and I want type-stable code.
I’m trying to improve my understanding of how the compiler behaves in those cases (and in general).
There are already different discussions here related to this topic:
- Type stable accumulator over heterogeneous collection
- Type instability with Union or heterogenous array/tuple
- How is the type of a heterogeneous vector determined when splatting and doing list comprehension?
But they do not fully address my doubts.
My question is associated with the following MWE, divided into two parts.
# First part
abstract type BaseType{T} end
getvalue(::BaseType, t) = error("Not implemented")
Base.eltype(::Type{<:BaseType{T}}) where T = T
veltype(::AbstractVector{<:BaseType{T}}) where T = T
struct A{T} <: BaseType{T}
x::T
end
A(x::T) where T = A{T}(x)
getvalue(s::A, t) = s.x + cos(t)
struct B{T} <: BaseType{T}
x::T
end
B(x::T) where T = B{T}(x)
getvalue(s::B, t) = s.x + sin(t)
function loop(v, t)
val = zero(veltype(v))
for vi in v
val += getvalue(vi, t)
end
return val
end
N = 10
v = [i % 2 == 0 ? A(1.0) : B(1.0) for i in 1:N];
t = 1.0
loop(v, t);
@btime loop($v, $t);
# Second part
struct C{T, N} <: BaseType{T}
x::T
end
C(x::T, n) where T = C{T, n}(x)
getvalue(s::C{T, N}, t) where {T, N} = s.x + t
loop(v, 0.0);
@btime loop($v, $t);
I believe the first part is a fine implementation that exploits function barriers, although I’m not sure if can be improved further. My understanding is that within the loop
function the getvalue
return type is statically dispatched, which is also what I got from @code_warntype
(e.g. in this case Body::Float64
).
Now adding the second part messes up the type stability completely, the Body
now is Any
and the whole program allocates, although C
is not used at all here, and veltype(v)
returns the correct type (e.g. Float64
).
Can someone help with this?