Function pointers & signatures

So afaik, it is accepted function pointers are not needed in Julia since we can pass functions, into other functions, causing the appropriate method to be compiled just in time, so that there shouldn’t be any performance hit.

However, and I might be missing somewhere here, this doesn’t really seem to work well if functions have an indefinite runtime, like in some indefinitely running simulation or a game, where the user might want to change the algorithm during runtime.

Let’s say I have some simulation that runs continuously on the data of some struct

mutable struct SomeStruct
algorithm::Function
data::...
end

function loop(s::SomeStruct)

  while true
    s.algorithm(s)
    if quitsim(s)
    return
  end
end

Now I’m not super well versed in C++, but I’m a bit familiar with it, and as far as my understanding goes, I could make s.algorithm a function pointer with a fixed signature, and just swap them out during runtime, without any performance hit.

Right now in julia, since functions are of a singleton type, my code is type instable and thus slow. The compiler doesn’t know anything (?) about the type of function that could be found in the field algorithm so I get generic code which is slow. I could pass the function to the loop function as an argument, but then I have the problem that I have to quit out of the loop and recompile it during the runtime of the simulation. I could also add the function type to the struct, but then I cannot swap it out anymore during the lifetime of the loop.

Another similar usecase is where I’m running something on a continuous loop or timer, but I want to a number of functions with the same signature that can be changed during runtime. As an example I have an interface with update functions that update their respective elements in the interface. I might want to open or close windows and add these update functions to an async loop run from a timer (such that they’re called e.g. 60 times per second) or some async loop. In julia this is again type instable since you’re only telling the compiler that it will find some functions it can call during runtime, and not what type of arguments they expect and what type they will return.

Now again, I’m just kind of familiar with C++, so maybe these designs are to be avoided in the first place. I’d be very interested to know how one could deal with these situations in a Julian way.

Also, could it make sense to expand the type system in julia to allow for precompiled methods with fixed signature? I.e. such that we can have vectors or fields that allow for any method of the given type, allowing for fast execution methods that are of unknown type (but known signature) during compile time.

Function pointers are used in Julia, typically through yuyichao/FunctionWrappers.jl (github.com) but I think @cfunction also works on most platforms.

1 Like

More accurately, providing a fixed signature potentially removes the need for runtime dispatch of calling the varying functions. However, that doesn’t mean it’s as performant as compiling for specific functions. The lack of inlining has an inherent runtime cost for the call, and it also prevents various optimizations at compile-time.

Sort of. If the runtime dispatch dominates the runtime, then you’re seeing a large hit. If the various compiled functions take much longer, then the runtime dispatch is negligible. Julia compiles per call signature, so “function barriers” and type assertions (not necessarily fixed ones, can be generic and known at compile-time) can minimize the type-instability and runtime dispatches, and isolate them from the efficient compiled code.

Still, there are many cases where it’s worth removing runtime dispatch by communicating more information. FunctionWrappers.jl lets you specify a call signature and contain various functions’ compiled code. Despite describing some important limitations, the documentation is still scant so I’m linking my recent comment on it, the rest of the thread is worth reading too.

3 Likes

Wow, that’s super cool. Thanks for the heads-up.

Yes sorry I wasn’t super clear, I’m of course aware that running a function from a pointer won’t be as fast as inlining and optimizing for a specific function. I meant that swapping in any function for another won’t be slower than just pointing to a specific one.

Thanks for the reply, I will check your comment out.