Exploring Julia 1.10 - Key Features and Updates

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.

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).

Improved latency

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:

  1. 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.)
  2. The specific offending token is pointed out with some context.
  3. 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 as a + end is an error, so too is count ++ 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!

Faster garbage collection

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