Iβm trying to implement a dataflow graph for basic arithmetic and encountering type instability for all but trivially simple graphs. How to rewrite this in a type-stable way?
module Inst # for "type-instability"
import Base.:*
abstract type Op end # trait; indicates the arithmetic operation performed
struct Sum <: Op end
struct Prod <: Op end
struct Node{S<:Op, C} # a node in the graph; C is the type of the children.
children::Vector{C}
end
Node(T::Type{S}, children::Vector{C}) where {S<:Op, C}= Node{T, C}(children)
*(g::Node{S, C}, xx) where {S, C}= Node{S,C}([xx*tt for tt in g.children])
*(xx, g::Node)= *(g, xx)
# apply executes the operation
apply(g::Node{S, C}) where {S <: Op,C}= apply(S(), g)
apply(::Sum, g)= sum(apply.(g.children))
apply(::Prod, g)= prod(apply.(g.children)) # recurse apply into children
apply(x::T) where {T<:Number}= x # evaluate leaf-nodes of graph
end
using .Inst
# verify correctness of implementation...
s= Inst.Node(Inst.Sum, [1,2])
@show Inst.apply(s)
p= Inst.Node(Inst.Prod, [3,4])
@show Inst.apply(p)
g= Inst.Node(Inst.Prod, [s, p])
@show Inst.apply(g)
@code_warntype Inst.apply(s) # type stable
@code_warntype Inst.apply(p) # type stable
julia> @code_warntype Inst.apply(g) #<-- why is there type instability here?
Variables
#self#::Core.Compiler.Const(Main.Inst.apply, false)
g::Main.Inst.Node{Main.Inst.Prod,Main.Inst.Node{S,Int64} where S<:Main.Inst.Style}
Body::Any
1 β %1 = ($(Expr(:static_parameter, 1)))()::Core.Compiler.Const(Main.Inst.Prod(), false)
β %2 = Main.Inst.apply(%1, g)::Any
βββ return %2
Profiling shows that apply(g)
is much slower, presumably due to type instability.
julia> using BenchmarkTools
julia> @btime Inst.apply(s) ;
@btime Inst.apply(g) ;
66.834 ns (1 allocation: 96 bytes)
julia> @btime Inst.apply(g) ;
251.530 ns (5 allocations: 320 bytes)