Memory leaks from compiled functions: Solved

Thanks, I think I get some of it now: you’re reusing the method table of a free-d function by renaming @pooledfunction’s input function expression to the cached gensym-ed name and assigning the method-edited function to the input name as a non-const global variable. You’re right that doesn’t make as many new function types and associated method tables, but I’m still not sure how much of the observed numbers are saving memory on method tables or the OS caching allocations in disk. free_memory can’t tell.

As mentioned, replaced methods aren’t GCed in order for world age interactivity, so that’s still going to accumulate in a program that indefinitely evals methods, which might defeat the purpose. Games are mostly AOT compiled and the interpreted parts don’t usually generate code, so the purpose might not be needed anyway. But if you do want to avoid making new methods while changing behavior, a couple things come to mind:

  1. Callable objects. You don’t change the method body at all in order to simplify the example, but if your method bodies would practically only change internal values that you don’t want through your arguments, you could simply instantiate new functions with those internal values. The method would take those functions and the internal values as input, so the compiler can’t optimize it as much as baked-in values, but that also means the same compiled method works for any of the functions.
julia> struct Logb{B<:Real}<:Function  b::B  end

julia> (lb::Logb)(x) = log(lb.b, x)

julia> log5 = Logb(5) # one of many possible functions
Logb{Int64}(5)

julia> log5(25)
2.0
  1. DynamicExpressions.jl lets you build a callable expression without an associated method, though it does make inputs a bit strange. You just have to provide the component operators and compile those, which should stabilize for a fixed set of numerical types no matter how many expressions you build. The timings in the README have user issues, but it is indeed pretty fast if most of the work is done by the operators. I’ve explained how this addresses the whole eval deal in a fuller answer to a related question, so I’ll also link that. DynamicExpressions also has a lot of other features, but some of it is stored in extensions so you wouldn’t have to load it unless you also involve the other trigger packages.
1 Like