Nice workflows for using and developing Julia 1.9+
This a brief description of some nice development workflows for Julia, particularly for versions 1.9 or greater. This workflows are usually fairly personal, so many other useful ones may exist.
juliaup
The juliaup tool is an alternative to official download and allows an easy handling of Julia installations, updates and versioning. On Windows, juliaup
is available from Windows Store. On Linux or Mac, install juliaup
with:
curl -fsSL https://install.julialang.org | sh
then, close the terminal, start it again. The juliaup
and julia
executables will be available in your path. By default, juliaup
installs the latest stable version of Julia, which as of the writing of this text was 1.8.5. We want to work with the upcoming 1.9 series, so we start by installing it:
juliaup add 1.9
which will currently install, as of today, the 1.9.0-rc1 version of Julia. Then, lets make it the default Julia:
juliaup default 1.9
Revise and startup.jl
Revise.jl
is a fundamental tool for Julia development and use. It allows one to track changes to files and, thus, to very effectively develop new functions, tune plots, etc, by editing an script in parallel to an active Julia section. Thus, add revise to your main environment:
julia> ] add Revise
(remembering that the ]
will take you to the package manager prompt: (@v1.9) pkg>
).
Next, let us guarantee that Revise
is always loaded on startup. Add it to (or create the file)
~/.julia/config/startup.jl
and to it the line
using Revise
which will make Revise
to be loaded on each Julia startup.
Why Revise
With Revise loaded, it is possible to edit/develop scripts simply modifying the script and re-running functions in an open Julia section. For example, given the script that generates some data and then plots it:
using Plots
function my_data(n)
data = randn(n)
return data
end
function my_plot(data)
plt = plot(data; label="My data"; linewidth=1)
return plt
end
If we save the script in a myplot.jl
file, and within Julia, we includet
(note the t
! - for "track"):
julia> includet("./myplot.jl")
julia> data = my_data(1000);
julia> my_plot(data)
we generate the plot. Then, without leaving the Julia REPL, we can change any property of the data or the plot in the script, save the file, and re-run the functions changed, and they will reflect automatically the updates to the file.
The video below illustrates such a feature, by changing the line width of the plot, and executing again the my_plot
function.
The video illustrates the use of Revise from within VSCode, which is also a recommended tool for an effective workflow, but is not required here nor will be discussed in this text. In any case, if you are using it, install the Julia extension.
The example above illustrates some advantages of splitting Julia code into functions. With that layout, the function my_plot
can be repeatedly executed at the REPL, tracking the changes made on the file. The same could be done with the data-generation function, for example, if the data has to be reloaded from different files, for example. Note, additionally, that it is good to structure Julia code in functions for performance reasons (functions get compiled to efficient native code), although in this example that is a essentially irrelevant.
Environments
Julia 1.9 makes it particularly appealing to use environments for specific tasks, because the compiled code of the libraries gets stored in a environment-specific manner, making the load times of the libraries quicker than in previous Julia versions. Besides, the use of environments allows one to obtain completely reproducible setups. Let us take the previous script, but we will load another large package DataFrames
, and use it to store the sample data we are creating:
using Plots
using DataFrames
function my_data(n)
data = DataFrame(:x => randn(n))
return data
end
function my_plot(data)
plt = plot(data.x; label="My data", linewidth=1)
return plt
end
Creating and installing packages
Since Plots
and DataFrames
are relatively heavy packages, they take a while to install and compile. We will do that within an new environment. First, create a directory that will contain the environment files. We choose to save the environments within a ~/.JuliaEnvironments
directory, but that is completely optional, environments are stored in regular directories:
mkdir ~/.JuliaEnvironments
mkdir ~/.JuliaEnvironments/mydataplots
The mydataplots
is the directory where the environment files will be automatically created.
Then, start Julia and
julia> ] # go to pkg prompt
(@v1.9) pkg> activate ~/.JuliaEnvironments/mydataplots
Activating new project at `~/.JuliaEnvironments/mydataplots`
(mydataplots) pkg>
and note that the pkg>
prompt reflects that the mydataplots
environment is activated. We now add the necessary packages, which can take some minutes, depending on the internet connection and speed of the computer:
(mydataplots) pkg> add Plots, DataFrames
Resolving package versions...
...
after the installation is finished, let us simulate the use of the packages for the first time, which may trigger additional compilation. Type backspace
to go back to the Julia prompt, and do:
julia> using Plots, DataFrames
[ Info: Precompiling Plots [91a5bcdd-55d7-5caf-9e0b-520d859cae80]
[ Info: Precompiling DataFrames [a93c6f00-e57d-5684-b7b6-d8193f3e46c0]
which may also take some time (it is well possible that the packages don't get precompiled again, on this first using
, but sometimes they are because of dependency version updates).
That's all for the installation part.
Using the environment
You can quit Julia, and let us move to the directory of the working script:
cd ~/Documents/mytestscript
Here we have the script.jl
containing the code shown above, using Plots
and DataFrames
, as example packages.
Now start Julia, and activate the mydataplots
environment, with:
julia> ] # go to pkg prompt
(@v1.9) pkg> activate /home/user/.JuliaEnvironments/mydataplots/
Activating project at `~/.JuliaEnvironments/mydataplots`
(mydataplots) pkg>
type backspace
go back to the Julia prompt, and include the script (here with includet
, assuming that Revise
is loaded by default):
julia> includet("./myscript.jl")
This should take now a couple of seconds. And the responsiveness of the function should be good:
julia> @time data = my_data(1000)
0.002078 seconds (33 allocations: 18.031 KiB, 94.87% compilation time)
1000×1 DataFrame
Row │ x
│ Float64
──────┼────────────
1 │ -2.41804
2 │ -0.51387
3 │ 0.953752
4 │ 0.738998
5 │ 0.973528
⋮ │ ⋮
997 │ 0.707327
998 │ 0.200788
999 │ -0.84872
1000 │ -1.49911
991 rows omitted
and
julia> @time my_plot(data)
0.635311 seconds (2.83 M allocations: 172.703 MiB, 9.92% gc time, 99.49% compilation time: 72% of which was recompilation)
Thus, in a few seconds, the script can be completely run, avoiding usual delays of recompilation of the packages involved, which happened often in previous versions of Julia.
Automatic activation
Now, let us automate the activation of the environment, by adding to the top of the script the following first line:
import Pkg; Pkg.activate("/home/user/.JuliaEnvironments/mydataplots") # added line
using Plots
... # script continues
Now, when including the script, it will automatically activate that environment, and use the packages installed for it. It is even possible to just execute the script from the command-line with an acceptable performance. The script, shown below, now contains the execution of the functions and saving the plot to a figure:
user@m3g:~/Documents/mytestscript% time julia myscript.jl
Activating project at `~/.JuliaEnvironments/mydataplots`
real 0m5,172s
...
The complete script is:
import Pkg; Pkg.activate("/home/user/.JuliaEnvironments/mydataplots")
using Plots
using DataFrames
function my_data(n)
data = DataFrame(:x => randn(n))
return data
end
function my_plot(data)
plt = plot(data.x; label="My data", linewidth=1)
return plt
end
data = my_data(1000)
plt = my_plot(data)
savefig(plt,"plot.png")
Of course, you can use the same environment for all scripts that require the same set of packages, with the same benefits.
It is not impossible that you get some recompilation of the packages from time to time if, in particular, new packages are loaded in the same environment. However, once the packages of the environment are stable, precompilation should only occur when trying to use the same environment in different version of Julia.