Compiling a Custom Sysimage

by Martin D. Maas, Ph.D

Compiling a custom sysimage can be a convenient way of reducing precompilation time, and for example to reduce the well known long-time-to-first-plot issue.

In a few words, a Sysimage is a Julia session dumped into a file. It contains precompiled packages, functions, and methods, which can be loaded into memory and used very easily, just as any fast compiled function.

Julia ships with a default sysimage, which only contains the standard library. That is the reason why packages in the standard library can be loaded really fast. For example:

@time using Pkg
0.111626 seconds (1.31 k allocations: 102.734 KiB, 5.12% compilation time)

Naturally, we would like to get the same speed when using other packages, which are not part of the standard library.

However, when I run the following commands in my Intel i7 laptop:

@time using Plots
@time (p = plot(rand(5), rand(5)); display(p))
3.780245 seconds (6.90 M allocations: 500.497 MiB, 7.18% gc time, 1.11% compilation time)
9.233954 seconds (17.33 M allocations: 990.041 MiB, 3.45% gc time, 16.27% compilation time)

it turns out I have to wait a grand total of 13 seconds for the JIT compiler to finish its job, before I get to see my first plot after starting a new Julia session. Subsequent plotting is, of course, very fast, as every function I need is already compiled.

This problem with slow precompilation can be solved by creating a custom sysimage with PackageCompiler.jl.

Here’s how to do it:

Step 1: Create a new project

Package Compiler will create a file based on the present environment, so it’s better to start with a clean one.

For this reason, start by creating a new project as discussed, for example, in the Creating a New Project section of this tutorial.

Make sure to activate it as well, and install the required dependencies. In our case, it will be just Plots.

Within a Julia REPL launched from our project’s folder, we do the following.

] 
pkg > activate .
(MyProject) pkg > add Plots
(MyProject) pkg > Ctrl+C

We should now have a Project.toml file in the new directory, with some content similar to:

[deps]
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

and also a Manifest.toml file automatically generated by Julia.

Step 2: Create a file with a representative workflow

It is not only packages that need to be precompiled, but actual functions within those packages. As Julia functions can be called with multiple data types, we need to give the compiler more information on what are the data types that we intend to use in our typicall workflow.

For our example, we want to run the following workflow of loading Plots and displaying some data:

# precompile_plots.jl
using Plots
p = plot(rand(5), rand(5))
display(p)

Step 3: Compile your Custom Sysimage with PackageCompiler.jl

Start a Julia session within your project’s folder.

For convenience, I prefer to use a single-threaded kernel, which can lead to longer compilation times, but allows me to work simultaneously on other things in my PC.

cd MyProject
julia --threads 1

In order to compiler our image, we need to run the following commands:

using PackageCompiler
create_sysimage(:Plots, sysimage_path="JuliaSysimage.so", precompile_execution_file="precompile_plots.jl")
[ Info: ===== Start precompile execution =====
[ Info: ===== End precompile execution =====
[ Info: PackageCompiler: creating system image object file, this might take a while...

This should take a little while (around four minutes in my old Laptop) and we get a file called JuliaSysimage.so of around 200 MB, which is our custom sysimage.

Step 4: Initialize Julia with the Custom Sysimage

We now need a way to tell Julia to load our image instead of the default one.

For example, in order to use our newly created sysimage when starting a Julia REPL from the terminal, we should pass the following option:

julia --sysimage JuliaSysimage.so

Now let’s test our time to first plot again:

@time using Plots
@time (p = plot(LinRange(0,1,5), rand(5),label="Fast Plot"); display(p))
0.000062 seconds (81 allocations: 5.984 KiB)
0.311673 seconds (167.38 k allocations: 3.263 MiB)

Yup! Now the time to first plot is now a fraction of a second!

Compiling and Using a Custom Sysimage in VSCode

Now, what happens if you are working inside an IDE like VSCode?

Interestingly, it looks like the Julia-VSCode team is seeking to automate the whole process detailed in this post. The Julia-VSCode extensions provides this documentation about sysimages.

We can produce a custom sysimage of the current environment by simply running the following commands in the command pallette (Ctrl+Shift+P):

Tasks: Run Build Task
Julia: Build custom sysimage for current environment

However, it should be noted that, at least at the time of writing this, this feature is still experimental. In particular, VSCode will sucessfully call PackageCompiler automatically, but there seems to be no easy way to include a “precompile_execution_file” directly from the VSCode interface. Check out this issue for news on this matter.

So, for the moment, we still have to create a custom sysimage from a terminal, using the above-mentioned four-step procedure.

Now, in order to get VSCode to automatically select our custom image when we start a REPL, we should make sure of the following:

  • The name of the custom image file should be “JuliaSysimage.so”, or “JuliaSysimage.dll” (in Windows).
  • Within the Julia extension settings, the option “use an existing custom sysimage when starting the REPL” should be enabled.
  • The folder in which the image file is located should be marked as the present active environment.

That’s it! Now you will automatically have a fast time to first from within VSCode!