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 ![]()
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 ![]()
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?