How to store functions in a structure in a proper way?

Hello!

I want to create a structure that stores (besides other data) some functions. It is convinient to store them together in a one structure.

But consider the following code (MWE):

using BenchmarkTools
struct Foo
    f::Function
end

function calculate(sys::Foo, x::Vector{Float64})::Nothing
    for el in x
        sys.f(el)
    end
end

function main()
    f(x::Float64)::Float64 = sin(x)
    sys = Foo(f)

    N = 100_000
    x = zeros(N)

    @time (
        for el in x
            sys.f(el)
        end
    )

    calculate(sys, x) # compilation
    @time calculate(sys, x)
    @code_warntype calculate(sys, x)
end
main()

Notice that when I pass sys into calculate function and then use sys.f, it allocates too much.

@code_warntype says:

MethodInstance for calculate(::Foo, ::Vector{Float64})
    from calculate(sys::Foo, x::Vector{Float64}) in Main at C:\Users\densh\Documents\Science\test.jl:6
  Arguments
    #self#::Core.Const(calculate)
    sys::Foo
    x::Vector{Float64}
  Locals
    @_4::Union{Nothing, Tuple{Float64, Int64}}
    el::Float64
  Body::Nothing
  1 ─ %1  = Main.Nothing::Core.Const(Nothing)
  │   %2  = x::Vector{Float64}
  │         (@_4 = Base.iterate(%2))
  │   %4  = (@_4 === nothing)::Bool
  │   %5  = Base.not_int(%4)::Bool
  └──       goto #4 if not %5
  2 ┄ %7  = @_4::Tuple{Float64, Int64}
  │         (el = Core.getfield(%7, 1))
  │   %9  = Core.getfield(%7, 2)::Int64
  │   %10 = Base.getproperty(sys, :f)::Function
  │         (%10)(el)
  │         (@_4 = Base.iterate(%2, %9))
  │   %13 = (@_4 === nothing)::Bool
  │   %14 = Base.not_int(%13)::Bool
  └──       goto #4 if not %14
  3 ─       goto #2
  4 ┄ %17 = Base.convert(%1, nothing)::Core.Const(nothing)
  │   %18 = Core.typeassert(%17, %1)::Core.Const(nothing)
  └──       return %18

where ::Function is highlighted with red color, which means that Function isn’t a non-concrete type (but how is it connected with allocatoins I still don’t understand).

So, my question is what am I do wrong and what is the proper way of “storing functions in a structures” in a sense of performance.

Thanks!

Hello and welcome to the community!

Try

struct Foo{F<:Function} 
    f::F
end

To make sure the field is concretely typed.

When the compiler cannot figure out the correct type at compile time you’ll typically see many additional allocations.

8 Likes

Thank you a lot! Responded very quickly:)

There are also a few options if you have multiple functions. Easiest would be

struct Foo{F<:Function,G<:Function}
    f::F
    g::G
end

just in case the multiple parameter syntax isn’t familiar yet :slightly_smiling_face:

1 Like