Capturing functions defined in global scope

The Performance Tips discuss the drawbacks of capturing global variables inside of functions. Are there any performance penalties for capturing functions inside of other functions? For example,

function predict(x)
    # Returns some value
end

function loss(x, label)
    return sum(abs2, predict(x) .- label)
end

Will capturing predict() in loss() result in any performance penalties?

(I’m aware that functions are const by default, but wanted to see if there were other considerations to be aware of.)

No, there isn’t. If there were, then the functions sum and abs2 and - would have the same problem, as would every other function in Base, and all other libraries too.

3 Likes

The only other thing is that if you use a closure then there is indeed a performance penalty.

For example, this is bad:

predict = x -> x^2

function loss(x, label)
    return sum(abs2, predict(x) .- label)
end
2 Likes

That is a bit unfortunate. I can appreciate why closures lead to boxing and hinder proper type inference. While it makes sense to me that predict = x -> x^2 formally creates a closure, the return type is perfectly inferrable from x alone, isn’t it? Then why does inference on loss struggle here?

The manual mentions closures in the Performance Tips · The Julia Language.
I just wouldn’t think to apply the given example to the case at hand.

Well, yes but what predict means is not predictable since anyone can just go and do global predict = x -> string(x) and now all of a sudden the return type is different.

4 Likes

Isn’t this because predict = x -> x^2 isn’t const. If you do

const predict = x -> x^2

or define predict inside a function, you are ok.

4 Likes

Yes

1 Like

Yes of course, silly me :sweat_smile:

Thanks, these are helpful clarifications!

So it seems like the main issue is that the variable holding the function needs to be const, correct? The documentation mentions that function and struct declarations automatically make the assignments const, but when doing predict = x -> x^2 the variable predict is dynamic.

Correct me if I’m wrong, but I think const is still needed even when predict is defined inside a function? For example,

function makepredict()
    predict = x -> x^2
    return predict
end

function g1(x)
    return x^2
end

g2 = makepredict()

const g3 = makepredict()

@btime for i=1:1000 g1(10) end # 0.877 ns (0 allocations: 0 bytes)
@btime for i=1:1000 g2(10) end # 6.766 μs (0 allocations: 0 bytes)
@btime for i=1:1000 g3(10) end # 0.877 ns (0 allocations: 0 bytes)

Yeah, I think you would say that the binding of the variable is const.

In this case, it is again the binding of the variable g2 which is non-const. What I meant was that inside a function you don’t need to do

function makepredict(x)
    const predict = x -> x^2  # don't need const here
    return predict(x)
end

If you bind the output of makepredict() to a variable in global scope, then const makes a difference again. So in global scope these are different:

g2 = makepredict()
const g3 = makepredict()

But if g2 = makepredict() happens inside a function, you don’t need const.

1 Like