Exploring Julia 1.10 - Key Features and Updates
Get to know the recent enhancements in Julia 1.10.
This post was written by Steven Whitaker.
Note that Julia 1.10 is no longer the most recent Julia version. To learn more about the most recent version, check out our series of posts about Julia releases.
A new version of the Julia programming language was just released! Version 1.10 is now the latest stable version of Julia.
This release is a minor release, meaning it includes language enhancements and bug fixes but should also be fully compatible with code written in previous Julia versions (from version 1.0 and onward).
In this post, we will check out some of the features and improvements introduced in this newest Julia version. Read the full post, or click on the links below to jump to the features that interest you.
- Improved Latency, or Getting Started Faster
- Better Error Messages
- Multithreaded Garbage Collection
- Timing Package Precompilation
- Broadcasting Defined for
CartesianIndex
If you are new to Julia (or just need a refresher), feel free to check out our Julia tutorial series, beginning with how to install Julia and VS Code.
Improved Latency, or Getting Started Faster
Julia 1.10 has improved latency, which means you can get started faster.
Two sources of latency historically have been slow in Julia: package loading and just-in-time code compilation. A classic example where this latency was readily noticeable was when trying to create a plot; consequently, this latency often is called the time to first plot (TTFP), or how long one has to wait before seeing a plot.
Note that the TTFP issue exists in the first place because Julia was designed with a trade-off in mind: by taking the time to compile a function the first time it is called, subsequent calls to the function can run at speeds comparable to C. This, however, leads to increased latency on the first call.
Recent Julia versions have been tackling this issue, and Julia 1.10 further improves latency.
Below is a screenshot of a slide shared
during the State of Julia talk at JuliaCon 2023.
It shows how the time it takes
to load Plots.jl
and then call plot
decreases when moving from Julia 1.8
to Julia 1.9
and then to Julia 1.10
(in this case, Julia 1.10
wasn't released yet,
so the alpha version was used).
I saw similar results on my computer comparing Julia 1.9.4 to Julia 1.10.0-rc1 (the first release candidate of Julia 1.10):
# Julia 1.9.4
julia> @time using Plots
1.278046 seconds (3.39 M allocations: 194.392 MiB, 10.10% gc time, 6.28% compilation time: 89% of which was recompilation)
julia> @time display(plot(1:10))
0.365514 seconds (246.08 k allocations: 16.338 MiB, 58.76% compilation time: 10% of which was recompilation)
# Julia 1.10.0-rc1
julia> @time using Plots
0.713279 seconds (1.42 M allocations: 97.684 MiB, 3.30% gc time, 15.26% compilation time: 86% of which was recompilation)
julia> @time display(plot(1:10))
0.257097 seconds (247.72 k allocations: 17.621 MiB, 6.29% gc time, 81.56% compilation time: 9% of which was recompilation)
It's amazing how much latency has been improved!
Better Error Messages
Julia 1.10 now uses JuliaSyntax.jl as the default parser, replacing the old Lisp-based parser.
Having a new parser doesn't change how the language runs, but the new parser does improve error messages, enabling easier debugging and creating a lower barrier to entry for new Julia users.
As an example, consider the following buggy code:
julia> count = 0;
julia> for i = 1:10
count++
end
Can you spot the error?
Julia 1.9 gives the following error message:
ERROR: syntax: unexpected "end"
Julia 1.10 gives the following:
ERROR: ParseError:
# Error @ REPL[2]:3:1
count++
end
└─┘ ── invalid identifier
There are at least three improvements to the error message:
- The file location of the offending token
is prominently displayed.
(
REPL[2]:3:1
means the second REPL entry, the third line, and the first character. This would be replaced with a file path and line and character numbers if the code were run in a file.) - The specific offending token is pointed out with some context.
- It is now clear that an identifier
(i.e., a variable name)
was expected
after
count++
. (Note that++
is a user-definable infix operator in Julia; so just asa + end
is an error, so too iscount ++ end
.)
Improved error messages are certainly a welcome addition!
Multithreaded Garbage Collection
Part of Julia's garbage collection is now parallelized in Julia 1.10, resulting in faster garbage collection.
Below is a screenshot of a slide shared
during the State of Julia talk at JuliaCon 2023.
It shows the percentage of time
a piece of code spent
doing garbage collection
in different Julia versions
(here the master
branch is a pre-release version of Julia 1.10).
The takeaway is that using threads
decreased garbage collection time!
The parallelization is implemented
using threads,
and the number of threads
available for garbage collection
can be specified when starting Julia
with the command line argument --gcthreads
.
For example,
to use four threads for garbage collection:
julia --gcthreads=4
By default,
--gcthreads
is half
the total number of threads
Julia is started with.
Experiment with different numbers of garbage collection threads to see what works best for your code.
Timing Package Precompilation
Timing how long individual packages take to precompile
is now easily achieved with
Pkg.precompile(timing = true)
.
In Julia 1.9,
Pkg.precompile
reported just the overall time precompilation took:
julia> using Pkg; Pkg.precompile()
Precompiling project...
20 dependencies successfully precompiled in 91 seconds. 216 already precompiled.
Pkg.precompile()
(without the timing
option)
behaves the same in Julia 1.10.
But now there is the option
to report the precompilation time
for individual packages:
julia> using Pkg; Pkg.precompile(timing = true)
Precompiling project...
19850.9 ms ✓ DataFrames
2858.4 ms ✓ Flux
26206.5 ms ✓ Plots
3 dependencies successfully precompiled in 49 seconds. 235 already precompiled.
Now it is easy to see what packages precompile faster than others!
Broadcasting Defined for CartesianIndex
Julia 1.10 now defines broadcasting
for CartesianIndex
objects.
A CartesianIndex
is a way
to represent an index
into a multidimensional array
and can be useful for
working with loops over arrays of arbitrary dimensionality.
Suppose we define the following:
julia> indices = [CartesianIndex(2, 3), CartesianIndex(4, 5)];
julia> I = CartesianIndex(1, 1);
In Julia 1.9,
attempting to broadcast over a CartesianIndex
(for example, indices .+ I
)
resulted in the following error:
ERROR: iteration is deliberately unsupported for CartesianIndex.
With broadcasting defined,
where previously we would have to wrap
the CartesianIndex
in a Tuple
(e.g., indices .+ (I,)
),
now the following works:
julia> indices .+ I
2-element Vector{CartesianIndex{2}}:
CartesianIndex(3, 4)
CartesianIndex(5, 6)
Summary
In this post, we learned about some of the new features and improvements introduced in Julia 1.10. Curious readers can check out the release notes for the full list of changes.
What are you most excited about in Julia 1.10? Let us know in the comments below!
Additional Links
- Julia v1.10 Release Notes
- Full list of changes made in Julia 1.10.
- Julia Basics for Programmers
- Series of blog posts covering Julia basics.