PackageCompiler.jl: What is it doing and how do I use it correctly?

Today I was playing with PackageCompiler.jl (PC) because I am wondering whether it could play a role in reducing startup times (sparked by the recent discussions about the worsening of Julia startup times ref). During my experiments, I realized that PC does not do what I expected and hope that some of you can shed some light on this :slight_smile:

Setup

versioninfo & envs
julia> versioninfo()
Julia Version 1.12.4
Commit 01a2eadb047 (2026-01-06 16:56 UTC)
Build Info:
  Official https://julialang.org release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 16 × AMD Ryzen 7 4800H with Radeon Graphics
  WORD_SIZE: 64
  LLVM: libLLVM-18.1.7 (ORCJIT, znver2)
  GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 16 virtual cores)

(@v1.12) pkg> st
Status `~/.julia/environments/v1.12/Project.toml`
  [6e4b80f9] BenchmarkTools v1.6.3
  [5fb14364] OhMyREPL v0.5.32
  [295af30f] Revise v3.13.2

(@packagecompiler) pkg> st
Status `~/.julia/environments/packagecompiler/Project.toml`
  [9b87118b] PackageCompiler v2.2.4

Generally, my goal was to improve the startup time of my standard REPL (so REPL + startup.jl + packages in default env which are Revise.jl, OhMyRepl.jl and BenchmarkTools.jl). Additionally, I wanted a smooth UX and not have common operation lag a bit on first occurence, like pressing a key, printing an error, using Pkg.status(). So I created a precompile file with julia --trace-compile compile-file, played around the REPL a bit and then used this to create a sysimage:

$ time julia -q -e 'Pkg.activate("packagecompiler";shared=true);import PackageCompiler;PackageCompiler.create_sysimage(;project=".julia/environments/v1.12/",sysimage_path="julia/v12-sysimg5",precompile_statements_file="compile-file-final")'
  Activating project at `~/.julia/environments/packagecompiler`
✔ [03m:32s] PackageCompiler: compiling incremental system image

real	4m24.571s
user	17m53.056s
sys	0m21.353s

Question 1: What exactly is in the sysimage?

I have a bit of knowledge about image-based systems from SBCL and thus thought that a Julia sysimage is the same as a SBCL image created by (save-lisp-and-die). That would include essentially the whole global state. This seems not to be the case though because my custom sysimage still needs to do import Pkg before I can use Pkg.

Question 2: Why does sysimage creation take ~5min?

Maybe the answer to the previous question explains this as well. What is PC doing? I used the default incremental=true, so in my mind PC basically just needs to dump the current image. Maybe perform a bit of additional precompilation for additional precompile statements/workloads. But even if it needs to precompile the loaded packages again, that shouldn’t take 5mins (maybe more like 1-2min I think). For reference, SBCL takes 2 seconds to dump the image:

$ time sbcl --noinform --eval '(sb-ext:save-lisp-and-die "test-sbcl-image")'

real	0m1.729s
user	0m1.546s
sys	0m0.183s

Bonus question: Since PC clearly does not ‘just dump the image’, would dumping the image more directly be possible for Julia as well?

Question 3: Why do I still get a lot of output from --trace-compile?

If I start Julia with my custom sysimage and --trace-compile and do the same rudimentary options as before, I still get almost as much output as before. I thought using the precompile file would include the code into the sysimage such that it does not get compiled again?

Question 4: Why are some of the operations still slow?

Probably related to the previous question. Creating the custom sysimage did not remove the lag for some operations:

  • first button press has no longer noticeable lag
  • Pkg.status() still has lag
  • first error message printed still has lag

While startup indeed got faster:

$ hyperfine 'julia -e "exit()"' 'julia --sysimage julia/v12-sysimg5 -e "exit()"'
Benchmark 1: julia -e "exit()"
  Time (mean ± σ):      1.147 s ±  0.013 s    [User: 1.651 s, System: 0.236 s]
  Range (min … max):    1.126 s …  1.166 s    10 runs
 
Benchmark 2: julia --sysimage julia/v12-sysimg5 -e "exit()"
  Time (mean ± σ):     423.9 ms ±  17.3 ms    [User: 990.1 ms, System: 193.6 ms]
  Range (min … max):   409.7 ms … 471.0 ms    10 runs
 
Summary
  julia --sysimage julia/v12-sysimg5 -e "exit()" ran
    2.71 ± 0.11 times faster than julia -e "exit()"

However using Pkg.status() is actually slower

$ hyperfine 'julia -e "Pkg.status()"' 'julia --sysimage julia/v12-sysimg5 -e "Pkg.status()"'
Benchmark 1: julia -e "Pkg.status()"
  Time (mean ± σ):      1.630 s ±  0.034 s    [User: 2.567 s, System: 0.314 s]
  Range (min … max):    1.573 s …  1.697 s    10 runs
 
Benchmark 2: julia --sysimage julia/v12-sysimg5 -e "Pkg.status()"
  Time (mean ± σ):      1.861 s ±  0.022 s    [User: 3.297 s, System: 0.335 s]
  Range (min … max):    1.832 s …  1.890 s    10 runs
 
Summary
  julia -e "Pkg.status()" ran
    1.14 ± 0.03 times faster than julia --sysimage julia/v12-sysimg5 -e "Pkg.status()"

Does that make sense to anybody?

How do I use PC correctly such that these operations no longer lag? Or am I misunderstanding PC and what it can do fundamentally?

So if someone could enlighten me or point me to resources that contain answers, I would be very thankful :slight_smile:
EDIT: A thought that came to my mind just after posting: Maybe REPL and Pkg are just not being included into my custom sysimage? Maybe adding them to the env explicitly would make a difference?

1 Like

Example for a script to create a system image: KiteModels.jl/test/create_sys_image.jl at main · OpenSourceAWE/KiteModels.jl · GitHub

One key aspect is, that you use a precompile execution file, like this:

precompile_execution_file=joinpath("test", "test_for_precompile.jl")

It should execute a typical task of your project.

And yes, PackageCompiler is slow. For my projects it takes about 10 minutes on a fast machine with many cores. And to be able to use 8 or more cores you need 32 GB RAM. The new juliac compiler is faster.

A good, custom system image can speed up the FTTX a lot, for example, by a factor of three. I use a custom system image if without it the FTTX time would exceed 10s.

I put most of a project’s dependencies into a custom system image, but not the project itself. I do not put plotting libraries into a custom system image, at least not Plots.jl or PythonPlot.jl, because that can cause issues.

2 Likes

Technical explanation for why Package Compilier takes so long? · Issue #736 · JuliaLang/PackageCompiler.jl

Not a satisfying answer and it predates the parallelization of native code generation in 1.10, but it does mention having to build base Julia. I have no idea how to make comparisons with builds of other things mentioned there, repo size or lines of source code is only loosely correlated with compilation times, especially across different language designs.

1 Like

Thanks @ufechner7 for the example. That matches what I think how PackageCompiler should work. However in my test I struggled to speed up the REPL experience.

Thanks @Benny for the reference. Apparently @tecosaur shares my sentiment that PackageCompiler is surprisingly slow :sweat_smile: