Generate function that calls sequence of functions in order to avoid recompilation

I have a type which looks like this:

mutable struct FQCTDipole{T<:Function} <: AbstractForceField
    terms::Vector{T}
    storage::FQCTDipoleStorage
    results::ForceFieldResults
end

The only part of this that matters if the Vector{Function} which is currently being evaluated (with simplified arguments) as follows:

function evaluate!(coords::Vector{MVector{3, Float64}}, ff::AbstractForceField)
    for i in eachindex(ff.terms)
        ff.terms[i](coords, ff)
    end
end

Importantly, this is designed in such a way that all the arguments to each term will be identical. Also, none of the functions return anything and instead mutate some buffers.

What I have learned, and seen some mention of since, is that the evaluate! function above will spend a huge amount of time compiling every time it is called . I guess this is because some piece of information is opaque to the compiler and hence it cannot guarantee the same functions will be called each time we loop through the terms array.

I can verify that the code gets dramatically faster and doesn’t recompile if I just make a function like this:

function composite_func(coords::Vector{MVector{3, Float64}}, ff::AbstractForceField)
    term_a!(coords, ff)
    term_b!(coords, ff)
    term_c!(coords, ff)
end

where term_a! would have been the first element of ff.terms. I am open to adding a field to my struct which contains this composite function and then changing evaluate! to just call this.

The problem is, I would like to be able to potentially change the functions which make up this composite function at runtime. I am aware that this will require recompiling the new composite function every time it changes and that’s fine.

This seems like a great use for generated functions, if I understand how they actually work, but I don’t really know how to go about doing this… I’ve tried various things that don’t seem to work.

Any help or suggestions would be greatly appreciated!

No it won’t. It will do dynamic dispatch, which is not the same thing as compilation.

Generated functions won’t help here — you have dynamic dispatch because your terms::Vector{T} is an abstractly typed container. It is equivalent to Vector{Function} because every function has a distinct type (unless your terms are all the same function!).

Do you have an array of FQCTDipole, all with different function terms? Or just one set of terms in a given calculation? If it’s only one type of function terms in a given calculation, you can make the function type(s) a part of the FQCTDipole type (e.g. as a tuple of functions).

Why use a vector of functions at all, as opposed to a single function that applies whatever terms you like (e.g. made of several terms composed with , or some more generic anonymous function)? (Do any of your calculations use the terms individually?)

One alternative is the FunctionWrappers package, which allows you to have typed functions that avoid dynamic dispatch.

So, I just learned that when I use the @time macro and one of the arguments is generated by a list comprehension, it will seemingly recompile the functions every time.

But, if I just pre-compute it and save it in a variable, then it will do what I intend. I’m basically trying to mimic a series of virtual function calls like one might do in C++. In any case, I guess the problem was how I was passing arguments to the function? I don’t understand that, but I guess I don’t have a problem after all.

This kind of OOP style is not what the language was designed for; generally you should try for a more idiomatic Julia style if you want good performance.