Disclamer:
- This package is not for end-users; it’s for package developers.
- This package does not really make package loading faster; it only delays the package loading to its first use. But sometimes, you never get a chance to use it.
Here I take Plots as an example, but it applies to any heavy package. This isn’t something that is unversally useful, but for certain applications, it provides a chance to cut off a lot of latencies because your users don’t need to use all the features.
This is still under the registration process but I can’t wait to shout it out here!
The first question: How fast would you expect using Plots
to be? Is 0.5s something practical? – Maybe, but when you don’t use Plots, 0s is the fastest.
But why should you using Plots
when you don’t use it? Another good question: because for package devlopers, they want to provide all the features, while some features might only be used by <1% of the users but have a large set of dependencies.
This is where LazyModules.jl become useful: what you need to do is add the small @lazy
macro before your normal import
command.
The following example can be found in the examples/
folder:
module MyLazyPkg
export generate_data, draw_figure
+using LazyModules
-import Plots
+@lazy import Plots
generate_data(n) = sin.(range(start=0, stop=5, length=n) .+ 0.1.*rand(n))
draw_figure(data) = Plots.plot(data, title="MyPkg Plot")
end
Now…
julia> @time using MyLazyPkg # 🚀🚀🚀🚀🚀
0.053273 seconds (154.16 k allocations: 8.423 MiB, 97.62% compilation time)
julia> x = @time generate_data(100); # 🚀
0.000006 seconds (2 allocations: 1.750 KiB)
But when you do the first plot, Plots
gets loaded and it’s still slow:
julia> @time draw_figure(x) # 💤💤
4.454738 seconds (13.82 M allocations: 897.071 MiB, 8.81% gc time, 49.97% compilation time)
Here 4.4s is the Plots
loading time plus the plot
TTFX time.
Caveats
This isn’t a real module; it is just a plain struct
with getproperty
overrided to mimic common module usage. Because of this, there are a few cases not supported. For instance, parametrized constructors are not supported.
julia> using LazyModules
[ Info: Precompiling LazyModules [8cdb02fc-e678-4876-92c5-9defec4f444e]
julia> @lazy import ImageCore as LazyImageCore
LazyModule(ImageCore)
julia> LazyImageCore.RGB(0.0, 0.0, 0.0)
RGB{Float64}(0.0,0.0,0.0)
julia> LazyImageCore.RGB{Float64}(0.0, 0.0, 0.0)
ERROR: TypeError: in Type{...} expression, expected UnionAll, got a value of type LazyModules.var"#f#4"{LazyModules.var"#f#3#5"{Symbol, Module}}
Stacktrace:
[1] top-level scope
@ REPL[5]:1
Also this introduces a few overhead ~80ns
per call, so don’t use it for trivial functions.
IMPORTANT: You’ll still need to eagerly load the core packages to ensure the caller can directly work on the function output without hitting the world age issues. See discussion below.