Performance and allocation with recursive tree-equations

I checked with a profiler and got

using BenchmarkTools

abstract type equation_nodes end

struct opera_node{L, R} <: equation_nodes
    arity::Int64; opera::Int64
    lef::L; rig::R

    opera_node(a::Int64, o::Int64) = new{Nothing, Nothing}(a, o, nothing, nothing)
    opera_node(a::Int64, o::Int64, l::L) where {L} = new{L, Nothing}(a, o, l, nothing)
    opera_node(a::Int64, o::Int64, l::L, r::R) where {L, R} = new{L, R}(a, o, l, r)
end

struct const_leav <: equation_nodes
    value::Float64
end

struct varia_leav <: equation_nodes
    value::Int64
end

function eval_equation(eq, vars, ops)
    if typeof(eq) <: const_leav
        zeros = vars[1] .- vars[1]  # to get a 0.0-tuple of appropriate length
        cur_val = zeros .+ eq.value # and ensure to return always same type

    elseif typeof(eq) <: varia_leav
        cur_val = vars[eq.value]

    elseif eq.arity == 1
        lef = eval_equation(eq.lef, vars, ops)
        cur_val = ops[eq.opera].(lef)

    elseif eq.arity == 2
        lef = eval_equation(eq.lef, vars, ops)
        rig = eval_equation(eq.rig, vars, ops)
        cur_val = ops[eq.opera].(lef, rig)
    else

        cur_val = nothing
    end

    return cur_val
end

@btime eval_equation(eq1, vars, ops) setup = (
    eq1 = opera_node(2, 1, varia_leav(1), varia_leav(2));
    vars = ((1.0, 2.0, 3.4, 4.1, 5.3, 6.3), (7.0, 2.0, 2.4, 1.1, 5.0, 6.4));
    ops = (+, -, *, /, cos) 
)

yielding

  1.200 ns (0 allocations: 0 bytes)

Edit: eliminated the last allocation via benchmark setup, now constant propagation does it’s job :slight_smile: