I have a tool/framework package that normally operates on “no-code” configuration files. Since users might need functionality that is not part of the core package at that time, the possibility exists to “load addons” during runtime. This is done by supplying small .jl files that either contain a few lines of code or a set of specific functions (callbacks/hooks). These are used either based on RuntimeGeneratedFunctions or by making use of Base.invokelatest.
However, that means every time a user starts a fresh “session” (most are using a Python wrapper that makes use of juliacall internally, leading to frequent restarts of Python scripts, etc.) all of this custom code is being compiled.
Is there any convenient / automated way that I could use to prevent that? I thought of making use of the “Startup Package” idea in PrecompileTools, but am not quite sure what a good way would be to abstract that away from users … Further I’m trying to keep initial loading times from exploding, since the initial import statement in Python already may take > 10 secs (depending on hardware) to startup juliacall and load the package, so I’d be interested in a way to not worsen that further.
A precompiled pkgimage is the correct way to do cache this code, but there currently isn’t really any support for turning any code other than a top-level package into such an image. It’s not hard in theory, but the machinery doesn’t really exist.
The code gets wrapped in gensym-ed modules (we are using JuliaSyntax behind the scenes to “parse & modify”), so maybe a way could be to make these more “permanent” and bake them into a sysimage? The problem here is that these take forever on some old hardware (even if just for that incremental step)… and every time a user wants to change something it goes from “small recompilation” to “rebuild the sysimage”.
Is there maybe any interaction that could be used? They share the same serialization with pkgimages right?
It’s definitely possible to do, but this stuff is tricky enough that I can’t really give you a step-by-step on how to do it. You’ll have to study base/loading.jl and understand the way that packages images work.
Note that RuntimeGeneratedFunctions are supported in package compilation, so if in the package compilation process you build an RGF it will be able to save and reload it. Using this you can precompile model code by turning it into a package.
I have a trivial wrapper module that contains a single function created from some code a user defined during runtime - do you mean that I automatically create a package from that (so probably the basic file structure + toml in a tmp directory) and then precompile this? I would then have to dynamically Pkg.add that as a dependency, but the next time the user starts everything (without any changes) the package could just be re-used without any compilation. Or did you mean something different?
You create a Julia package, put the code that builds the RGF and does a first solve call into the top level of the module, and then using MyModelPackage you will see that the first run time on the model will already be compiled.