Function as argument

Please direct me to documentation on how to pass a function as argument to another function in type stable manner. Thx!

There’s nothing special to do, just pass it.

3 Likes

Ah? When I pass as the function as argument, the code below does a lot of allocations.

Taking the function as global variable from main scope instead, makes allocations disappear.

Using @code_warntype does not reveal any concerns.

I am missing something.

mySourceFct(x,y) = x+y 

function generateLocalVector(element::Element, sourceFct::Function)
    p1 = element.p1; p2 = element.p2; p3 = element.p3;  
    e1 = element.e1; e2 = element.e2; e3 = element.e3;  
    area = element.area 
    Iloc = SVector(e1, e2, e3) 
    # use broadcast for the lines below instead 
    f1 = area/3*sourceFct(p1.x,p1.y)
    f2 = area/3*sourceFct(p2.x,p2.y)
    f3 = area/3*sourceFct(p3.x,p3.y)
    floc = SVector(f1, f2, f3) 
    return Iloc, floc
end

function generateVector(mesh, sourceFct::Function)
 
    #..recover number of elements  
    N::Int64 = length(mesh.Elements)
    nnodes = mesh.nnodes 
     
    #..preallocate the memory for local matrix contributions 
    f = zeros(Float64,nnodes)

    for i = 1:N #..loop over number of elements..
        element::Element = mesh.Elements[i]
        Iloc, floc = generateLocalVector(element,sourceFct)
        f[Iloc] += floc 
    end
       
    return f; 
end

Check out this part of the performance tips.

This should be fixed by changing

function generateVector(mesh, sourceFct::Function)

to

function generateVector(mesh, sourceFct::F) where F

If there are still problems, you might need to make the same change to generateLocalVector, but that is probably not needed because the function is called directly by it.

4 Likes

Thx! Allocations are now indeed wiped out. Is compiler performance something to worry over here?

I wouldn’t usually worry about the compiler in limited cases like this. The place where you might get in trouble is throwing a zillion different functions at this. Compiling with specialization for a handful (or even tens or maybe hundreds) of functions won’t be noticeable… but the runtime performance that the specializations provide certainly is!

Maybe somebody else can provide a more thorough commentary as to why this behavior is default. My understanding is that it’s very useful in situations, but this performance trap catches just about everyone sooner or later.

Do I read the docs correctly if I understood that this non-specialisation of the outside method is happening because your are not actually using the function and just passing it through to the inside method ?

So that in the OP state of the code, the inner method indeed specializes on the function, but the outer one does not, and therefore cannot call the specialized inner one ?

2 Likes

Correct. Reminds me of a past thread; TLDR yes this exception is unintuitive, but specializing on functions by default improves performance in uncommon situations yet severely bloats compilation of caller chains generally, and chained callee inlining can and often removes the caller chain and nonspecialization issue for argument functions fixed at compile-time e.g. foo(x, y) = map(+, x, y).

1 Like