Array of functions - is there a way to avoid allocations performance penalty?

Why do you want to have an array at all? As far as I understood, the 30-40 functions don’t change from person to person. Hence, you can just write 30-40 lines of source code (you do need to define each of these functions anyways).

An alternative would be to go metaprogramming: Either by using a macro or a generated function. In that case, you could write one big

funs = global_fun_dict[S]
#emit code calling funs...
end

Then you would maintain one big global Dict{Any,Vector{Any}}. This Dict would only be queried at compile time, so does not need to be typed.

FunctionWrappers should probably do the job as well.

Thanks. My goal, which until now I had thought was probably easy to accomplish, is for the list of functions and their ordering to be specified in a json file:

  1. read the json file to get the names of the functions
  2. use getfield to put them into a tuple or static array
  3. loop through the data records
  4. for each record, loop through the functions and call them
  5. etc.

Because I know how to do 1 & 2, my example focused on doing 3 & 4 in combination, with a loop, without a performance penalty. It sounds like doing that is not easy. I’ll read about FunctionWrappers.

Yeah, if the set of functions you want to run is only known at run-time (when you read the .json file), then FunctionWrappers is probably a good choice. FunctionWrappers should allow you to loop over a vector of functions (provided all of them have the same signature) with little to no performance penalty.

1 Like

Many thanks, all. I am new to Julia and not a programmer by trade so it is a steep learning curve. I do now have a solution that appears to work (below) and am investigating others.

I have started to investigate the Dict approach mentioned above. My early efforts found that iterating through a Dict produces the same memory allocation as looping through arrays or tuples, but I did not use metaprogramming. I am reading about FunctionWrappers but don’t fully understand it yet. The “using type for dispatch” and the “tuple of structs” approaches mentioned above sound like they would provide maximum flexibility and I will learn how to use them.

As for the short term, what I have learned is that unrolling the inner loop (as suggested by the timings above) avoids the large excessive memory allocations that looping through a tuple or array requires. The code below does this, and runs a fair amount faster (1/3 time) than the not-unrolled loop, in my simple example of looping through only 2 functions. It also does not require a lot of hard (for me) to read code. So I have a workable solution I can use while I learn more about Julia.

using Unrolled
const cfuns = (sin, cos)
@unroll function test_loop(n::Int64, cfuns::Tuple)
    t = 0.0
    for i = 1:n
        @unroll for j in 1:length(cfuns)
            t += cfuns[j](i)
        end
    end
    return t
end
@time test_loop(10^6, cfuns) # 20.1k allocations
@time test_loop(10^6, cfuns) # 6 allocations