Introduction to the
Julia language

Julia tutorial @ SC26

Mosè Giordano

UCL

2026-11-16

Follow along

https://juliaparallel.org/julia-hpc-tutorial-sc26/

Introduction

Defining and calling functions

Short form for one-line functions:

add(x, y) = x + y

add(1, 2)
3
add(1.0, 2.0)
3.0

Defining and calling functions (cont.)

Long form for more complex functions:

function axpy!(y, a, x)
    for idx in eachindex(x, y)
        y[idx] = muladd(a, x[idx], y[idx])
    end
    return y
end

y = zeros(Float32, 2)
a = 3.14f0
x = [1f0, 2f0]

axpy!(y, a, x)
2-element Vector{Float32}:
 3.14
 6.28

Multiple dispatch

In a nutshell

myfun(a::Integer, b::Integer) = a - b
myfun(a::AbstractFloat, b::AbstractFloat) = a / b

myfun(1, 2)
-1
myfun(1.0, 2.0)
0.5
julia> myfun(1, 2.0)
ERROR: MethodError: no method matching myfun(::Int64, ::Float64)
The function `myfun` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  myfun(::AbstractFloat, ::AbstractFloat)
   @ Main REPL[2]:1
  myfun(::Integer, ::Integer)
   @ Main REPL[1]:1

Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

In a nutshell (cont.)

Type annotations in signatures are only used for dispatch, not performance. Multiple dispatch enables composability:

Rock, paper, scissors

abstract type Shape end
struct Rock     <: Shape end
struct Paper    <: Shape end
struct Scissors <: Shape end
play(::Paper,   ::Rock)     = "Paper wins"
play(::Paper,   ::Scissors) = "Scissors wins"
play(::Rock,    ::Scissors) = "Rock wins"
play(::T,       ::T) where {T<: Shape} = "Tie, try again"
play(a::Shape, b::Shape) = play(b, a) # Commutativity


play(Paper(), Scissors())
"Scissors wins"
play(Rock(), Rock())
"Tie, try again"
play(Rock(), Paper())
"Paper wins"

Arrays and
Linear Algebra

Arrays

Julia has built-in support for multi-dimensional tensors:

x = [1.0, 2.0, 3.0, 4.0];
y = [5.0, 6.0, 7.0, 8.0];

(x + y)'
1×4 adjoint(::Vector{Float64}) with eltype Float64:
 6.0  8.0  10.0  12.0
x[4]
4.0
y[2:3]
2-element Vector{Float64}:
 6.0
 7.0

Performance note

Slicing makes a copy!

Linear Algebra

The LinearAlgebra stdlib exposes BLAS functionalities with a simple interface:

using LinearAlgebra

A = [1.0 2.0; 3.0 4.0]; B = [5.0 6.0; 7.0 8.0];
A * B
2×2 Matrix{Float64}:
 19.0  22.0
 43.0  50.0
qr(B)
LinearAlgebra.QRCompactWY{Float64, Matrix{Float64}, Matrix{Float64}}
Q factor: 2×2 LinearAlgebra.QRCompactWYQ{Float64, Matrix{Float64}, Matrix{Float64}}
R factor:
2×2 Matrix{Float64}:
 -8.60233  -9.9973
  0.0      -0.232495

Broadcasting

for loops are fast, but for convenience you can use broadcasting, which enables syntactic loop fusion:

f(x) = 3 * sin(x) * cos(x) - 5 * exp(-x) + 2

X = [1.0 2.0; 3.0 4.0];
X .= f.(2 .* X.^2 .+ 6 .* X.^3 .- sqrt.(X))
2×2 Matrix{Float64}:
 3.48135   3.05936
 0.500912  0.527089
a = [1.0, 2.0];
b = [3.0, 4.0];

a .* b'
2×2 Matrix{Float64}:
 3.0  4.0
 6.0  8.0

Benchmarking and profiling

The @time macro

Julia comes with a simple macro @time for measuring elapsed time of sufficiently long-running functions:

@time sleep(0.1)
  0.101182 seconds (6 allocations: 224 bytes)

Caveat

This may not provide useful information for very quick functions (resolution is of the order of ~microsecond):

using LinearAlgebra
A = randn(100);
norm(A) # warm up
@time norm(A)
  0.000006 seconds (1 allocation: 16 bytes)
11.33955619203718

The BenchmarkTools.jl package

For more accurate timing of functions, packages like BenchmarkTools.jl and ChairMarks.jl provide more advanced tools:

using BenchmarkTools
@benchmark norm(A) setup=(A = randn(100))
BenchmarkTools.Trial: 10000 samples with 977 evaluations per sample.
 Range (minmax):  67.014 ns106.075 ns   GC (min … max): 0.00% … 0.00%
 Time  (median):     70.071 ns                GC (median):    0.00%
 Time  (mean ± σ):   70.506 ns ±   3.440 ns   GC (mean ± σ):  0.00% ± 0.00%

    ▅▄▄▄▄▄▄▅  ▂                     ▁▁▁▂▁                     ▂
  ▅▄█████████▅▅▆▇▅▄▅▅▁▄▃▃▄▅▄▅▇█▇███████▇▇▇▇▇▇██▇▇▆▇▆▅▃▅▆▅▅▅ █
  67 ns         Histogram: log(frequency) by time      84.8 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

Profiling Julia code

julia> using Profile, LinearAlgebra

julia> N = 4_000; A = randn(N, N); B = randn(N, N); C = randn(N, N);

julia> Profile.clear()

julia> Profile.@profile mul!(C, A, B);

julia> Profile.print()
Overhead ╎ [+additional indent] Count File:Line  Function
=========================================================
6    @Base/client.jl:561  _start()
6    @Base/client.jl:586  repl_main
6    @Base/client.jl:499  run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_file::Bool)
6    @Base/client.jl:478  run_std_repl(REPL::Module, quiet::Bool, banner::Symbol, history_file::Bool)
6    @REPL/src/REPL.jl:639  run_repl
6    @REPL/src/REPL.jl:653  #run_repl#50
   ╎    ╎ 6    @REPL/src/REPL.jl:424  start_repl_backend
   ╎    ╎  6    @REPL/src/REPL.jl:427  #start_repl_backend#41
   ╎    ╎   6    @REPL/src/REPL.jl:452  repl_backend_loop
   ╎    ╎    6    @REPL/src/REPL.jl:330  eval_user_input
   ╎    ╎     6    @REPL/src/REPL.jl:305  toplevel_eval_with_hooks
   ╎    ╎    ╎ 6    @REPL/src/REPL.jl:312  toplevel_eval_with_hooks
   ╎    ╎    ╎  6    @REPL/src/REPL.jl:312  toplevel_eval_with_hooks
   ╎    ╎    ╎   6    @REPL/src/REPL.jl:308  toplevel_eval_with_hooks
  1╎    ╎    ╎    6    @REPL/src/REPL.jl:301  __repl_entry_eval_expanded_with_loc
   ╎    ╎    ╎     5    @LinearAlgebra/src/matmul.jl:265  mul!(C::Matrix{Float64}, A::Matrix{Float64}, B::Matrix{Float64})
   ╎    ╎    ╎    ╎ 5    @LinearAlgebra/src/matmul.jl:297  mul!
   ╎    ╎    ╎    ╎  5    @LinearAlgebra/src/matmul.jl:328  _mul!
   ╎    ╎    ╎    ╎   5    @LinearAlgebra/src/matmul.jl:507  generic_matmatmul_wrapper!(C::Matrix{Float64}, tA::Char, tB::Char, A::Matrix{Float64}, B::Matrix{Float64}, α::Bool, β::Bool, val::Val{LinearAlgebra.BlasFlag.GEMM})
   ╎    ╎    ╎    ╎    5    @LinearAlgebra/src/matmul.jl:527  _syrk_herk_gemm_wrapper!
   ╎    ╎    ╎    ╎     5    @LinearAlgebra/src/matmul.jl:808  gemm_wrapper!
  4╎    ╎    ╎    ╎    ╎ 5    @LinearAlgebra/src/blas.jl:1648  gemm!(transA::Char, transB::Char, alpha::Float64, A::Matrix{Float64}, B::Matrix{Float64}, beta::Float64, C::Matrix{Float64})
591  @Base/task.jl:839  task_done_hook(t::Task)
591  @Base/task.jl:1199  wait()
591591  @Base/task.jl:1187  poptask(W::Base.IntrusiveLinkedListSynchronized{Task})
Total snapshots: 1182. Utilization: 50% across all threads and tasks. Use the `groupby` kwarg to break down by thread and/or task.

Profiling Julia code (cont.)

julia> Profile.print(; C=true)
Overhead ╎ [+additional indent] Count File:Line  Function
=========================================================
591  @juliasrc/task.c:1260  start_task
591  @juliasrc/task.c:345  jl_finish_task
591  @juliasrc/julia.h:2391  jl_apply
591  @julialib/julia/sys.so:?  jfptr_task_done_hook_36828.1
591  @Base/task.jl:839  task_done_hook(t::Task)
591  @Base/task.jl:1199  wait()
   ╎    ╎ 591  @Base/task.jl:1187  poptask(W::Base.IntrusiveLinkedListSynchronized{Task})
   ╎    ╎  591  @juliasrc/scheduler.c:523  ijl_task_get_next
   ╎    ╎   591  /workspace/srcdir/libuv/src/unix/thread.c:822  uv_cond_wait
   ╎    ╎    591  /lib/x86_64-linux-gnu/libc.so.6:?  pthread_cond_wait
   ╎    ╎     591  /lib/x86_64-linux-gnu/libc.so.6:?
   ╎    ╎    ╎ 591  /lib/x86_64-linux-gnu/libc.so.6:?
591╎    ╎    ╎  591  /lib/x86_64-linux-gnu/libc.so.6:?
585585  @julialib/julia/libopenblas64_.so:?  dgemm_kernel_HASWELL
6    /workspace/srcdir/glibc-2.17/csu/../sysdeps/x86_64/start.S:123
6    /lib/x86_64-linux-gnu/libc.so.6:?  __libc_start_main
6    /lib/x86_64-linux-gnu/libc.so.6:?
6    /cache/build/tester-amdci4-14/julialang/julia-release-1-dot-12/cli/loader_exe.c:58  main
6    @juliasrc/jlapi.c:1139  jl_repl_entrypoint
6    @juliasrc/jlapi.c:971  true_main
   ╎    ╎ 6    @juliasrc/julia.h:2391  jl_apply
   ╎    ╎  6    @julialib/julia/sys.so:?  jfptr__start_31204.1
   ╎    ╎   6    @Base/client.jl:561  _start()
   ╎    ╎    6    @Base/client.jl:586  repl_main
   ╎    ╎     6    @Base/client.jl:499  run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_file::Bool)
   ╎    ╎    ╎ 6    @juliasrc/builtins.c:881  jl_f_invokelatest
   ╎    ╎    ╎  6    @juliasrc/julia.h:2391  jl_apply
   ╎    ╎    ╎   6    @julialib/julia/sys.so:?  jfptr_run_std_repl_62877.1
   ╎    ╎    ╎    6    @Base/client.jl:478  run_std_repl(REPL::Module, quiet::Bool, banner::Symbol, history_file::Bool)
   ╎    ╎    ╎     6    up/julia-1.12.1+0.x64.linux.gnu/share/julia/compiled/v1.12/REPL/u0gqU_UDl4g.so:?  jfptr_run_repl_18594.1
   ╎    ╎    ╎    ╎ 6    @REPL/src/REPL.jl:639  run_repl
   ╎    ╎    ╎    ╎  6    @REPL/src/REPL.jl:653  #run_repl#50
   ╎    ╎    ╎    ╎   6    @REPL/src/REPL.jl:424  start_repl_backend
   ╎    ╎    ╎    ╎    6    @REPL/src/REPL.jl:427  #start_repl_backend#41
   ╎    ╎    ╎    ╎     6    @REPL/src/REPL.jl:452  repl_backend_loop
   ╎    ╎    ╎    ╎    ╎ 6    @REPL/src/REPL.jl:330  eval_user_input
   ╎    ╎    ╎    ╎    ╎  6    @REPL/src/REPL.jl:305  toplevel_eval_with_hooks
   ╎    ╎    ╎    ╎    ╎   6    @REPL/src/REPL.jl:312  toplevel_eval_with_hooks
   ╎    ╎    ╎    ╎    ╎    6    @REPL/src/REPL.jl:312  toplevel_eval_with_hooks
   ╎    ╎    ╎    ╎    ╎     6    @REPL/src/REPL.jl:308  toplevel_eval_with_hooks
   ╎    ╎    ╎    ╎    ╎    ╎ 6    @juliasrc/builtins.c:881  jl_f_invokelatest
   ╎    ╎    ╎    ╎    ╎    ╎  6    @juliasrc/julia.h:2391  jl_apply
   ╎    ╎    ╎    ╎    ╎    ╎   6    @REPL/src/REPL.jl:301  __repl_entry_eval_expanded_with_loc
   ╎    ╎    ╎    ╎    ╎    ╎    6    @juliasrc/toplevel.c:1035  jl_toplevel_eval_flex
   ╎    ╎    ╎    ╎    ╎    ╎     6    @juliasrc/interpreter.c:898  jl_interpret_toplevel_thunk
   ╎    ╎    ╎    ╎    ╎    ╎    ╎ 5    @juliasrc/interpreter.c:558  eval_body
   ╎    ╎    ╎    ╎    ╎    ╎    ╎  5    @juliasrc/interpreter.c:581  eval_body
   ╎    ╎    ╎    ╎    ╎    ╎    ╎   5    @juliasrc/interpreter.c:243  eval_value
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    5    @juliasrc/interpreter.c:123  do_call
   ╎    ╎    ╎    ╎    ╎    ╎    ╎     5    @juliasrc/julia.h:2391  jl_apply
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎ 5    @LinearAlgebra/src/matmul.jl:265  mul!(C::Matrix{Float64}, A::Matrix{Float64}, B::Matrix{Float64})
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎  5    @LinearAlgebra/src/matmul.jl:297  mul!
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎   5    @LinearAlgebra/src/matmul.jl:328  _mul!
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    5    @LinearAlgebra/src/matmul.jl:507  generic_matmatmul_wrapper!(C::Matrix{Float64}, tA::Char, tB::Char, A::Matrix{Float64}, B::Matrix{Float64}, α::Bool, β::Bool, val::Val{LinearAlgebra.BlasF…
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎     5    @LinearAlgebra/src/matmul.jl:527  _syrk_herk_gemm_wrapper!
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎ 5    @LinearAlgebra/src/matmul.jl:808  gemm_wrapper!
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎  5    @LinearAlgebra/src/blas.jl:1648  gemm!(transA::Char, transB::Char, alpha::Float64, A::Matrix{Float64}, B::Matrix{Float64}, beta::Float64, C::Matrix{Float64})
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎   5    @julialib/julia/libopenblas64_.so:?  dgemm_64_
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    5    @julialib/julia/libopenblas64_.so:?  dgemm_thread_nn
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎     5    @julialib/julia/libopenblas64_.so:?  gemm_driver.isra.0
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎ 5    @julialib/julia/libopenblas64_.so:?  exec_blas
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎  5    @julialib/julia/libopenblas64_.so:?  inner_thread
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎   1    @julialib/julia/libopenblas64_.so:?  dgemm_beta_HASWELL
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    1    @julialib/julia/libopenblas64_.so:?
  3╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎   3    @julialib/julia/libopenblas64_.so:?  dgemm_itcopy_HASWELL
  1╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎    ╎   1    @julialib/julia/libopenblas64_.so:?  dgemm_oncopy_HASWELL
   ╎    ╎    ╎    ╎    ╎    ╎    ╎ 1    @juliasrc/interpreter.c:707  eval_body
   ╎    ╎    ╎    ╎    ╎    ╎    ╎  1    @juliasrc/interpreter.c:194  eval_stmt_value
   ╎    ╎    ╎    ╎    ╎    ╎    ╎   1    @juliasrc/interpreter.c:243  eval_value
   ╎    ╎    ╎    ╎    ╎    ╎    ╎    1    @juliasrc/interpreter.c:122  do_call
  1╎    ╎    ╎    ╎    ╎    ╎    ╎     1    @juliasrc/interpreter.c:201  eval_value
Total snapshots: 1182. Utilization: 50% across all threads and tasks. Use the `groupby` kwarg to break down by thread and/or task.

Using external profilers

Julia is also compatible with third-party profilers:

Performance tuning and introspection

LLVM IR

@code_llvm add(1, 2)
; Function Signature: add(Int64, Int64)
;  @ /home/runner/work/julia-hpc-tutorial-sc26/julia-hpc-tutorial-sc26/01-Intro-to-Julia/presentations/intro-julia/index.qmd:48 within `add`
define i64 @julia_add_9296(i64 signext %"x::Int64", i64 signext %"y::Int64") local_unnamed_addr #0 {
top:
; ┌ @ int.jl:87 within `+`
   %0 = add i64 %"y::Int64", %"x::Int64"
   ret i64 %0
; └
}
@code_llvm add(1.0, 2.0)
; Function Signature: add(Float64, Float64)
;  @ /home/runner/work/julia-hpc-tutorial-sc26/julia-hpc-tutorial-sc26/01-Intro-to-Julia/presentations/intro-julia/index.qmd:48 within `add`
define double @julia_add_9353(double %"x::Float64", double %"y::Float64") local_unnamed_addr #0 {
top:
; ┌ @ float.jl:492 within `+`
   %0 = fadd double %"x::Float64", %"y::Float64"
   ret double %0
; └
}

LLVM IR (cont.)

code_llvm(axpy!, (Vector{Float32}, Float32, Vector{Float32}); debuginfo=:none)
; Function Signature: axpy!(Array{Float32, 1}, Float32, Array{Float32, 1})
define nonnull ptr @"julia_axpy!_9386"(ptr noundef nonnull align 8 dereferenceable(24) %"y::Array", float %"a::Float32", ptr noundef nonnull align 8 dereferenceable(24) %"x::Array") local_unnamed_addr #0 {
top:
  %"new::OneTo" = alloca [1 x i64], align 8
  %"new::OneTo1" = alloca [1 x i64], align 8
  %"x::Array.size_ptr" = getelementptr inbounds nuw i8, ptr %"x::Array", i64 16
  %"x::Array.size.0.copyload" = load i64, ptr %"x::Array.size_ptr", align 8
  store i64 %"x::Array.size.0.copyload", ptr %"new::OneTo", align 8
  %"y::Array.size_ptr" = getelementptr inbounds nuw i8, ptr %"y::Array", i64 16
  %"y::Array.size.0.copyload" = load i64, ptr %"y::Array.size_ptr", align 8
  store i64 %"y::Array.size.0.copyload", ptr %"new::OneTo1", align 8
  %.not.not = icmp eq i64 %"y::Array.size.0.copyload", %"x::Array.size.0.copyload"
  br i1 %.not.not, label %L22, label %L19

L19:                                              ; preds = %top
  call void @j_throw_eachindex_mismatch_indices_9389(ptr nonnull @"jl_global#9390.jit", ptr nocapture nonnull readonly %"new::OneTo", ptr nocapture nonnull readonly %"new::OneTo1") #7
  unreachable

L22:                                              ; preds = %top
  %0 = icmp slt i64 %"x::Array.size.0.copyload", 1
  br i1 %0, label %L99, label %iter.check

iter.check:                                       ; preds = %L22
  %memoryref_data = load ptr, ptr %"x::Array", align 8
  %memoryref_data14 = load ptr, ptr %"y::Array", align 8
  %min.iters.check = icmp samesign ult i64 %"x::Array.size.0.copyload", 4
  br i1 %min.iters.check, label %L31.preheader, label %vector.memcheck

vector.memcheck:                                  ; preds = %iter.check
  %1 = shl i64 %"x::Array.size.0.copyload", 2
  %scevgep = getelementptr i8, ptr %memoryref_data14, i64 %1
  %scevgep67 = getelementptr i8, ptr %memoryref_data, i64 %1
  %bound0 = icmp ult ptr %memoryref_data14, %scevgep67
  %bound1 = icmp ult ptr %memoryref_data, %scevgep
  %found.conflict = and i1 %bound0, %bound1
  br i1 %found.conflict, label %L31.preheader, label %vector.main.loop.iter.check

vector.main.loop.iter.check:                      ; preds = %vector.memcheck
  %min.iters.check68 = icmp samesign ult i64 %"x::Array.size.0.copyload", 32
  br i1 %min.iters.check68, label %vec.epilog.ph, label %vector.ph

vector.ph:                                        ; preds = %vector.main.loop.iter.check
  %n.mod.vf = and i64 %"x::Array.size.0.copyload", 28
  %n.vec = and i64 %"x::Array.size.0.copyload", 9223372036854775776
  %broadcast.splatinsert = insertelement <8 x float> poison, float %"a::Float32", i64 0
  %broadcast.splat = shufflevector <8 x float> %broadcast.splatinsert, <8 x float> poison, <8 x i32> zeroinitializer
  br label %vector.body

vector.body:                                      ; preds = %vector.body, %vector.ph
  %index = phi i64 [ 0, %vector.ph ], [ %index.next, %vector.body ]
  %.reass = shl i64 %index, 2
  %2 = getelementptr inbounds i8, ptr %memoryref_data, i64 %.reass
  %3 = getelementptr inbounds nuw i8, ptr %2, i64 32
  %4 = getelementptr inbounds nuw i8, ptr %2, i64 64
  %5 = getelementptr inbounds nuw i8, ptr %2, i64 96
  %wide.load = load <8 x float>, ptr %2, align 4
  %wide.load69 = load <8 x float>, ptr %3, align 4
  %wide.load70 = load <8 x float>, ptr %4, align 4
  %wide.load71 = load <8 x float>, ptr %5, align 4
  %6 = fmul contract <8 x float> %broadcast.splat, %wide.load
  %7 = fmul contract <8 x float> %broadcast.splat, %wide.load69
  %8 = fmul contract <8 x float> %broadcast.splat, %wide.load70
  %9 = fmul contract <8 x float> %broadcast.splat, %wide.load71
  %10 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %.reass
  %11 = getelementptr inbounds nuw i8, ptr %10, i64 32
  %12 = getelementptr inbounds nuw i8, ptr %10, i64 64
  %13 = getelementptr inbounds nuw i8, ptr %10, i64 96
  %wide.load72 = load <8 x float>, ptr %10, align 4
  %wide.load73 = load <8 x float>, ptr %11, align 4
  %wide.load74 = load <8 x float>, ptr %12, align 4
  %wide.load75 = load <8 x float>, ptr %13, align 4
  %14 = fadd contract <8 x float> %6, %wide.load72
  %15 = fadd contract <8 x float> %7, %wide.load73
  %16 = fadd contract <8 x float> %8, %wide.load74
  %17 = fadd contract <8 x float> %9, %wide.load75
  store <8 x float> %14, ptr %10, align 4
  store <8 x float> %15, ptr %11, align 4
  store <8 x float> %16, ptr %12, align 4
  store <8 x float> %17, ptr %13, align 4
  %index.next = add nuw i64 %index, 32
  %18 = icmp eq i64 %index.next, %n.vec
  br i1 %18, label %middle.block, label %vector.body

middle.block:                                     ; preds = %vector.body
  %cmp.n = icmp eq i64 %"x::Array.size.0.copyload", %n.vec
  br i1 %cmp.n, label %L99, label %vec.epilog.iter.check

vec.epilog.iter.check:                            ; preds = %middle.block
  %ind.end = or disjoint i64 %n.vec, 1
  %min.epilog.iters.check = icmp eq i64 %n.mod.vf, 0
  br i1 %min.epilog.iters.check, label %L31.preheader, label %vec.epilog.ph

vec.epilog.ph:                                    ; preds = %vec.epilog.iter.check, %vector.main.loop.iter.check
  %vec.epilog.resume.val = phi i64 [ %n.vec, %vec.epilog.iter.check ], [ 0, %vector.main.loop.iter.check ]
  %n.vec77 = and i64 %"x::Array.size.0.copyload", 9223372036854775804
  %19 = or disjoint i64 %n.vec77, 1
  %broadcast.splatinsert80 = insertelement <4 x float> poison, float %"a::Float32", i64 0
  %broadcast.splat81 = shufflevector <4 x float> %broadcast.splatinsert80, <4 x float> poison, <4 x i32> zeroinitializer
  br label %vec.epilog.vector.body

vec.epilog.vector.body:                           ; preds = %vec.epilog.vector.body, %vec.epilog.ph
  %index78 = phi i64 [ %vec.epilog.resume.val, %vec.epilog.ph ], [ %index.next83, %vec.epilog.vector.body ]
  %.reass85 = shl i64 %index78, 2
  %gep = getelementptr i8, ptr %memoryref_data, i64 %.reass85
  %wide.load79 = load <4 x float>, ptr %gep, align 4
  %20 = fmul contract <4 x float> %broadcast.splat81, %wide.load79
  %gep87 = getelementptr i8, ptr %memoryref_data14, i64 %.reass85
  %wide.load82 = load <4 x float>, ptr %gep87, align 4
  %21 = fadd contract <4 x float> %20, %wide.load82
  store <4 x float> %21, ptr %gep87, align 4
  %index.next83 = add nuw i64 %index78, 4
  %22 = icmp eq i64 %index.next83, %n.vec77
  br i1 %22, label %vec.epilog.middle.block, label %vec.epilog.vector.body

vec.epilog.middle.block:                          ; preds = %vec.epilog.vector.body
  %cmp.n84 = icmp eq i64 %"x::Array.size.0.copyload", %n.vec77
  br i1 %cmp.n84, label %L99, label %L31.preheader

L31.preheader:                                    ; preds = %vec.epilog.middle.block, %vec.epilog.iter.check, %vector.memcheck, %iter.check
  %value_phi6.ph = phi i64 [ %ind.end, %vec.epilog.iter.check ], [ 1, %iter.check ], [ 1, %vector.memcheck ], [ %19, %vec.epilog.middle.block ]
  %23 = add nuw i64 %"x::Array.size.0.copyload", 1
  %24 = sub i64 %23, %value_phi6.ph
  %25 = sub i64 %"x::Array.size.0.copyload", %value_phi6.ph
  %xtraiter = and i64 %24, 7
  %lcmp.mod.not = icmp eq i64 %xtraiter, 0
  br i1 %lcmp.mod.not, label %L31.prol.loopexit, label %L31.prol

L31.prol:                                         ; preds = %L31.prol, %L31.preheader
  %value_phi6.prol = phi i64 [ %31, %L31.prol ], [ %value_phi6.ph, %L31.preheader ]
  %prol.iter = phi i64 [ %prol.iter.next, %L31.prol ], [ 0, %L31.preheader ]
  %26 = shl i64 %value_phi6.prol, 2
  %memoryref_byteoffset.prol = add i64 %26, -4
  %memoryref_data10.prol = getelementptr inbounds i8, ptr %memoryref_data, i64 %memoryref_byteoffset.prol
  %27 = load float, ptr %memoryref_data10.prol, align 4
  %28 = fmul contract float %"a::Float32", %27
  %memoryref_data22.prol = getelementptr inbounds i8, ptr %memoryref_data14, i64 %memoryref_byteoffset.prol
  %29 = load float, ptr %memoryref_data22.prol, align 4
  %30 = fadd contract float %28, %29
  store float %30, ptr %memoryref_data22.prol, align 4
  %31 = add i64 %value_phi6.prol, 1
  %prol.iter.next = add i64 %prol.iter, 1
  %prol.iter.cmp.not = icmp eq i64 %prol.iter.next, %xtraiter
  br i1 %prol.iter.cmp.not, label %L31.prol.loopexit, label %L31.prol

L31.prol.loopexit:                                ; preds = %L31.prol, %L31.preheader
  %value_phi6.unr = phi i64 [ %value_phi6.ph, %L31.preheader ], [ %31, %L31.prol ]
  %32 = icmp ult i64 %25, 7
  br i1 %32, label %L99, label %L31

L31:                                              ; preds = %L31, %L31.prol.loopexit
  %value_phi6 = phi i64 [ %68, %L31 ], [ %value_phi6.unr, %L31.prol.loopexit ]
  %33 = shl i64 %value_phi6, 2
  %memoryref_byteoffset = add i64 %33, -4
  %memoryref_data10 = getelementptr inbounds i8, ptr %memoryref_data, i64 %memoryref_byteoffset
  %34 = load float, ptr %memoryref_data10, align 4
  %35 = fmul contract float %"a::Float32", %34
  %memoryref_data22 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %memoryref_byteoffset
  %36 = load float, ptr %memoryref_data22, align 4
  %37 = fadd contract float %35, %36
  store float %37, ptr %memoryref_data22, align 4
  %memoryref_data10.1 = getelementptr inbounds i8, ptr %memoryref_data, i64 %33
  %38 = load float, ptr %memoryref_data10.1, align 4
  %39 = fmul contract float %"a::Float32", %38
  %memoryref_data22.1 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %33
  %40 = load float, ptr %memoryref_data22.1, align 4
  %41 = fadd contract float %39, %40
  store float %41, ptr %memoryref_data22.1, align 4
  %memoryref_byteoffset.2 = add i64 %33, 4
  %memoryref_data10.2 = getelementptr inbounds i8, ptr %memoryref_data, i64 %memoryref_byteoffset.2
  %42 = load float, ptr %memoryref_data10.2, align 4
  %43 = fmul contract float %"a::Float32", %42
  %memoryref_data22.2 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %memoryref_byteoffset.2
  %44 = load float, ptr %memoryref_data22.2, align 4
  %45 = fadd contract float %43, %44
  store float %45, ptr %memoryref_data22.2, align 4
  %memoryref_byteoffset.3 = add i64 %33, 8
  %memoryref_data10.3 = getelementptr inbounds i8, ptr %memoryref_data, i64 %memoryref_byteoffset.3
  %46 = load float, ptr %memoryref_data10.3, align 4
  %47 = fmul contract float %"a::Float32", %46
  %memoryref_data22.3 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %memoryref_byteoffset.3
  %48 = load float, ptr %memoryref_data22.3, align 4
  %49 = fadd contract float %47, %48
  store float %49, ptr %memoryref_data22.3, align 4
  %memoryref_byteoffset.4 = add i64 %33, 12
  %memoryref_data10.4 = getelementptr inbounds i8, ptr %memoryref_data, i64 %memoryref_byteoffset.4
  %50 = load float, ptr %memoryref_data10.4, align 4
  %51 = fmul contract float %"a::Float32", %50
  %memoryref_data22.4 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %memoryref_byteoffset.4
  %52 = load float, ptr %memoryref_data22.4, align 4
  %53 = fadd contract float %51, %52
  store float %53, ptr %memoryref_data22.4, align 4
  %memoryref_byteoffset.5 = add i64 %33, 16
  %memoryref_data10.5 = getelementptr inbounds i8, ptr %memoryref_data, i64 %memoryref_byteoffset.5
  %54 = load float, ptr %memoryref_data10.5, align 4
  %55 = fmul contract float %"a::Float32", %54
  %memoryref_data22.5 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %memoryref_byteoffset.5
  %56 = load float, ptr %memoryref_data22.5, align 4
  %57 = fadd contract float %55, %56
  store float %57, ptr %memoryref_data22.5, align 4
  %memoryref_byteoffset.6 = add i64 %33, 20
  %memoryref_data10.6 = getelementptr inbounds i8, ptr %memoryref_data, i64 %memoryref_byteoffset.6
  %58 = load float, ptr %memoryref_data10.6, align 4
  %59 = fmul contract float %"a::Float32", %58
  %memoryref_data22.6 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %memoryref_byteoffset.6
  %60 = load float, ptr %memoryref_data22.6, align 4
  %61 = fadd contract float %59, %60
  store float %61, ptr %memoryref_data22.6, align 4
  %62 = add i64 %value_phi6, 7
  %63 = shl i64 %62, 2
  %memoryref_byteoffset.7 = add i64 %63, -4
  %memoryref_data10.7 = getelementptr inbounds i8, ptr %memoryref_data, i64 %memoryref_byteoffset.7
  %64 = load float, ptr %memoryref_data10.7, align 4
  %65 = fmul contract float %"a::Float32", %64
  %memoryref_data22.7 = getelementptr inbounds i8, ptr %memoryref_data14, i64 %memoryref_byteoffset.7
  %66 = load float, ptr %memoryref_data22.7, align 4
  %67 = fadd contract float %65, %66
  store float %67, ptr %memoryref_data22.7, align 4
  %68 = add i64 %value_phi6, 8
  %exitcond.not.7 = icmp eq i64 %62, %"x::Array.size.0.copyload"
  br i1 %exitcond.not.7, label %L99, label %L31

L99:                                              ; preds = %L31, %L31.prol.loopexit, %vec.epilog.middle.block, %middle.block, %L22
  ret ptr %"y::Array"
}

Native code

@code_native add(1, 2)
   .file   "add"
    .section    .ltext,"axl",@progbits
    .globl  julia_add_9459                  # -- Begin function julia_add_9459
    .p2align    4
    .type   julia_add_9459,@function
julia_add_9459:                         # @julia_add_9459
; Function Signature: add(Int64, Int64)
; ┌ @ /home/runner/work/julia-hpc-tutorial-sc26/julia-hpc-tutorial-sc26/01-Intro-to-Julia/presentations/intro-julia/index.qmd:48 within `add`
# %bb.0:                                # %top
    #DEBUG_VALUE: add:x <- $rdi
    #DEBUG_VALUE: add:y <- $rsi
    push rbp
    mov  rbp, rsp
; │┌ @ int.jl:87 within `+`
    lea  rax, [rdi + rsi]
    pop  rbp
    ret
.Lfunc_end0:
    .size   julia_add_9459, .Lfunc_end0-julia_add_9459
; └└
                                        # -- End function
    .section    ".note.GNU-stack","",@progbits
@code_native add(1.0, 2.0)
   .file   "add"
    .section    .ltext,"axl",@progbits
    .globl  julia_add_9512                  # -- Begin function julia_add_9512
    .p2align    4
    .type   julia_add_9512,@function
julia_add_9512:                         # @julia_add_9512
; Function Signature: add(Float64, Float64)
; ┌ @ /home/runner/work/julia-hpc-tutorial-sc26/julia-hpc-tutorial-sc26/01-Intro-to-Julia/presentations/intro-julia/index.qmd:48 within `add`
# %bb.0:                                # %top
    #DEBUG_VALUE: add:x <- $xmm0
    #DEBUG_VALUE: add:y <- $xmm1
    push rbp
    mov  rbp, rsp
; │┌ @ float.jl:492 within `+`
    vaddsd   xmm0, xmm0, xmm1
    pop  rbp
    ret
.Lfunc_end0:
    .size   julia_add_9512, .Lfunc_end0-julia_add_9512
; └└
                                        # -- End function
    .type   ".L+Core.Float64#9514",@object  # @"+Core.Float64#9514"
    .section    .lrodata,"al",@progbits
    .p2align    3, 0x0
".L+Core.Float64#9514":
    .quad   ".L+Core.Float64#9514.jit"
    .size   ".L+Core.Float64#9514", 8

.set ".L+Core.Float64#9514.jit", 140647856963424
    .size   ".L+Core.Float64#9514.jit", 8
    .section    ".note.GNU-stack","",@progbits

Native code (cont.)

code_native(axpy!, (Vector{Float32}, Float32, Vector{Float32}); debuginfo=:none)
   .file   "axpy!"
    .section    .ltext,"axl",@progbits
    .globl  "julia_axpy!_9540"              # -- Begin function julia_axpy!_9540
    .p2align    4
    .type   "julia_axpy!_9540",@function
"julia_axpy!_9540":                     # @"julia_axpy!_9540"
; Function Signature: axpy!(Array{Float32, 1}, Float32, Array{Float32, 1})
# %bb.0:                                # %top
    #DEBUG_VALUE: axpy!:y <- [$rdi+0]
    #DEBUG_VALUE: axpy!:a <- $xmm0
    #DEBUG_VALUE: axpy!:x <- [$rsi+0]
    push rbp
    mov  rbp, rsp
    sub  rsp, 16
    mov  rax, qword ptr [rsi + 16]
    mov  qword ptr [rbp - 16], rax
    mov  rcx, qword ptr [rdi + 16]
    mov  qword ptr [rbp - 8], rcx
    cmp  rcx, rax
    jne  .LBB0_20
# %bb.1:                                # %L22
    test rax, rax
    jle  .LBB0_19
# %bb.2:                                # %iter.check
    mov  rcx, qword ptr [rsi]
    mov  rdx, qword ptr [rdi]
    mov  esi, 1
    cmp  rax, 4
    jb   .LBB0_14
# %bb.3:                                # %vector.memcheck
    lea  r9, [rcx + 4*rax]
    lea  r8, [rdx + 4*rax]
    cmp  rdx, r9
    setb r9b
    cmp  rcx, r8
    setb r8b
    test r9b, r8b
    jne  .LBB0_14
# %bb.4:                                # %vector.main.loop.iter.check
    movabs   r8, 9223372036854775776
    cmp  rax, 32
    jae  .LBB0_9
# %bb.5:
    xor  r9d, r9d
    jmp  .LBB0_6
.LBB0_9:                                # %vector.ph
    vbroadcastss ymm1, xmm0
    mov  r9, rax
    and  r9, r8
    xor  esi, esi
    .p2align    4
.LBB0_10:                               # %vector.body
                                        # =>This Inner Loop Header: Depth=1
    vmovups  ymm2, ymmword ptr [rcx + 4*rsi]
    vmovups  ymm3, ymmword ptr [rcx + 4*rsi + 32]
    vmovups  ymm4, ymmword ptr [rcx + 4*rsi + 64]
    vmovups  ymm5, ymmword ptr [rcx + 4*rsi + 96]
    vfmadd213ps  ymm2, ymm1, ymmword ptr [rdx + 4*rsi] # ymm2 = (ymm1 * ymm2) + mem
    vfmadd213ps  ymm3, ymm1, ymmword ptr [rdx + 4*rsi + 32] # ymm3 = (ymm1 * ymm3) + mem
    vfmadd213ps  ymm4, ymm1, ymmword ptr [rdx + 4*rsi + 64] # ymm4 = (ymm1 * ymm4) + mem
    vfmadd213ps  ymm5, ymm1, ymmword ptr [rdx + 4*rsi + 96] # ymm5 = (ymm1 * ymm5) + mem
    vmovups  ymmword ptr [rdx + 4*rsi], ymm2
    vmovups  ymmword ptr [rdx + 4*rsi + 32], ymm3
    vmovups  ymmword ptr [rdx + 4*rsi + 64], ymm4
    vmovups  ymmword ptr [rdx + 4*rsi + 96], ymm5
    add  rsi, 32
    cmp  r9, rsi
    jne  .LBB0_10
# %bb.11:                               # %middle.block
    cmp  rax, r9
    je   .LBB0_19
# %bb.12:                               # %vec.epilog.iter.check
    test al, 28
    je   .LBB0_13
.LBB0_6:                                # %vec.epilog.ph
    add  r8, 28
    vbroadcastss xmm1, xmm0
    and  r8, rax
    mov  rsi, r8
    or   rsi, 1
    .p2align    4
.LBB0_7:                                # %vec.epilog.vector.body
                                        # =>This Inner Loop Header: Depth=1
    vmovups  xmm2, xmmword ptr [rcx + 4*r9]
    vfmadd213ps  xmm2, xmm1, xmmword ptr [rdx + 4*r9] # xmm2 = (xmm1 * xmm2) + mem
    vmovups  xmmword ptr [rdx + 4*r9], xmm2
    add  r9, 4
    cmp  r8, r9
    jne  .LBB0_7
# %bb.8:                                # %vec.epilog.middle.block
    cmp  rax, r8
    je   .LBB0_19
    jmp  .LBB0_14
.LBB0_13:
    or   r9, 1
    mov  rsi, r9
.LBB0_14:                               # %L31.preheader
    mov  r9d, eax
    sub  r9d, esi
    mov  r8, rax
    sub  r8, rsi
    inc  r9d
    and  r9d, 7
    je   .LBB0_16
    .p2align    4
.LBB0_15:                               # %L31.prol
                                        # =>This Inner Loop Header: Depth=1
    vmovss   xmm1, dword ptr [rcx + 4*rsi - 4] # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi - 4] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi - 4], xmm1
    inc  rsi
    dec  r9
    jne  .LBB0_15
.LBB0_16:                               # %L31.prol.loopexit
    cmp  r8, 7
    jb   .LBB0_19
# %bb.17:                               # %L31.preheader1
    lea  rdx, [rdx + 4*rsi + 24]
    lea  rcx, [rcx + 4*rsi + 24]
    sub  rax, rsi
    mov  rsi, -1
    .p2align    4
.LBB0_18:                               # %L31
                                        # =>This Inner Loop Header: Depth=1
    vmovss   xmm1, dword ptr [rcx + 4*rsi - 24] # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi - 24] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi - 24], xmm1
    vmovss   xmm1, dword ptr [rcx + 4*rsi - 20] # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi - 20] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi - 20], xmm1
    vmovss   xmm1, dword ptr [rcx + 4*rsi - 16] # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi - 16] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi - 16], xmm1
    vmovss   xmm1, dword ptr [rcx + 4*rsi - 12] # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi - 12] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi - 12], xmm1
    vmovss   xmm1, dword ptr [rcx + 4*rsi - 8] # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi - 8] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi - 8], xmm1
    vmovss   xmm1, dword ptr [rcx + 4*rsi - 4] # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi - 4] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi - 4], xmm1
    vmovss   xmm1, dword ptr [rcx + 4*rsi]   # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi], xmm1
    vmovss   xmm1, dword ptr [rcx + 4*rsi + 4] # xmm1 = mem[0],zero,zero,zero
    vfmadd213ss  xmm1, xmm0, dword ptr [rdx + 4*rsi + 4] # xmm1 = (xmm0 * xmm1) + mem
    vmovss   dword ptr [rdx + 4*rsi + 4], xmm1
    add  rsi, 8
    cmp  rax, rsi
    jne  .LBB0_18
.LBB0_19:                               # %L99
    mov  rax, rdi
    add  rsp, 16
    pop  rbp
    vzeroupper
    ret
.LBB0_20:                               # %L19
    movabs   rdi, offset ".Ljl_global#9544.jit"
    movabs   rax, offset j_throw_eachindex_mismatch_indices_9543
    lea  rsi, [rbp - 16]
    lea  rdx, [rbp - 8]
    call rax
.Lfunc_end0:
    .size   "julia_axpy!_9540", .Lfunc_end0-"julia_axpy!_9540"
                                        # -- End function
.set ".Ljl_global#9544.jit", 140647879754544
    .size   ".Ljl_global#9544.jit", 8
    .section    ".note.GNU-stack","",@progbits

Performance tips

General advices for improving performance of Julia code:

  • avoid accessing (untyped) global variables: put code in functions, don’t work in global scope
  • Julia has a garbage collector (GC) for safe automatic memory management, but in some cases it can get in the way of performance: do fully in-place operations in hot loops to avoid the GC to kick in and achieve best performance
  • the compiler may not always be able to prove array indexing operations are in bounds: you may want to use @inbounds to forcibly disable bounds checking (use with caution!). Double check this is actually necessary with @code_llvm, and use generic abstracts (like eachindex) whenever possible
  • slicing an array makes a copy, if you want to take a view instead use @view
  • avoid type-instabilities

Shared-memory parallelism

Task-based parallelism

using Base.Threads

@time t = @spawn begin # `@spawn` returns right away
    sleep(2)
    3 + 3
end

@time fetch(t) # `fetch` waits for the task to finish
  0.000098 seconds (70 allocations: 3.703 KiB)
  1.969231 seconds (10 allocations: 544 bytes)
6

Task-based parallelism (cont.)

using LinearAlgebra, BenchmarkTools, Base.Threads

@show nthreads()

BLAS.set_num_threads(1) # Fix number of BLAS threads

function tmap(fn, itr)
    # for each i ∈ itr, spawn a task to compute fn(i)
    tasks = map(i -> @spawn(fn(i)), itr)
    # fetch and return all the results
    return fetch.(tasks)
end

M = [rand(100,100) for i in 1:(8 * nthreads())];

@btime  map(svdvals, $M) samples=10 evals=3;
@btime tmap(svdvals, $M) samples=10 evals=3;
nthreads() = 4
  16.478 ms (418 allocations: 4.31 MiB)
  6.941 ms (587 allocations: 4.32 MiB)

Multi-threaded for loops

using ChunkSplitters, Base.Threads, BenchmarkTools

function sum_threads(fn, data; nchunks=nthreads())
    psums = zeros(eltype(data), nchunks)
    @threads for (c, elements) in enumerate(chunks(data; n=nchunks))
        psums[c] = sum(fn, elements)
    end
    return sum(psums)
end

v = randn(20_000_000);

@btime sum(sin, $v);
@btime sum_threads(sin, $v);
  256.206 ms (0 allocations: 0 bytes)
  82.882 ms (27 allocations: 1.75 KiB)

Good practices

Packaging

Julia has a built-in package manager:

julia> # Press ] to enter the Pkg REPL mode

(@v1.12) pkg> activate MyLocalEnvironment
  Activating new project at `/private/tmp/MyLocalEnvironment`

(MyLocalEnvironment) pkg> add Example
    Updating registry at `/var/folders/v2/hmy3kzgj4tb3xsy8qkltxd0r0000gn/T/tmp.tmYxNNrwBP/registries/General.toml`
   Resolving package versions...
   Installed Example ─ v0.5.5
    Updating `/private/tmp/MyLocalEnvironment/Project.toml`
  [7876af07] + Example v0.5.5
    Updating `/private/tmp/MyLocalEnvironment/Manifest.toml`
  [7876af07] + Example v0.5.5
Precompiling packages finished.
  1 dependency successfully precompiled in 1 seconds

(MyLocalEnvironment) pkg> status
Status `/private/tmp/MyLocalEnvironment/Project.toml`
  [7876af07] Example v0.5.5

Virtual environments

Virtual environments are defined by two files:

  • Project.toml: only top-level dependencies, with compatibility specifications (automatically generated by Pkg, but can be edited by users)
  • Manifest.toml: full snapshot of all packages in the environment, for reproducibility (fully machine-generated, do not touch it)

Virtual environments (cont.)

Project.toml:

[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
ChunkSplitters = "ae650224-84b6-46f8-82ea-d812ca08434e"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Profile = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
quarto_jll = "b7163347-bfae-5fd9-aba4-19f139889d78"

[compat]
BenchmarkTools = "1.6"
ChunkSplitters = "3.1"
quarto_jll = "1.8"

Virtual environments (cont.)

Manifest.toml:

# This file is machine-generated - editing it directly is not advised

julia_version = "1.13.0"
manifest_format = "2.1"
project_hash = "5a75629e977f736554d543dc8c97367508941dd3"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
version = "1.11.0"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
version = "1.11.0"

[[deps.BenchmarkTools]]
deps = ["Compat", "JSON", "Logging", "PrecompileTools", "Printf", "Profile", "Statistics", "UUIDs"]
git-tree-sha1 = "9670d3febc2b6da60a0ae57846ba74670290653f"
registries = "General"
uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
version = "1.8.0"

[[deps.ChunkSplitters]]
git-tree-sha1 = "1c52c8e2673edc030191177ff1aee42d25149acb"
registries = "General"
uuid = "ae650224-84b6-46f8-82ea-d812ca08434e"
version = "3.2.0"

[[deps.Compat]]
deps = ["TOML", "UUIDs"]
git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad"
registries = "General"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "4.18.1"
weakdeps = ["Dates", "LinearAlgebra"]

    [deps.Compat.extensions]
    CompatLinearAlgebraExt = "LinearAlgebra"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.5.5+2"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
version = "1.11.0"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
version = "1.11.0"

[[deps.JLLWrappers]]
deps = ["Artifacts", "Preferences"]
git-tree-sha1 = "7204148362dafe5fe6a273f855b8ccbe4df8173e"
registries = "General"
uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
version = "1.8.0"

[[deps.JSON]]
deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"]
git-tree-sha1 = "88352712893ec50bee3680605891eaf0e9ed6368"
registries = "General"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "1.8.0"

    [deps.JSON.extensions]
    JSONArrowExt = ["ArrowTypes"]

    [deps.JSON.weakdeps]
    ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd"

[[deps.JuliaSyntaxHighlighting]]
deps = ["StyledStrings"]
uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011"
version = "1.12.0"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
version = "1.11.0"

[[deps.LinearAlgebra]]
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
version = "1.13.0"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
version = "1.11.0"

[[deps.Markdown]]
deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
version = "1.11.0"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.30+0"

[[deps.Parsers]]
deps = ["Dates", "PrecompileTools"]
git-tree-sha1 = "663e8b48b789916221e0765393b289ca6c88f24e"
registries = "General"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "3.0.0"

[[deps.PrecompileTools]]
deps = ["Preferences"]
git-tree-sha1 = "edbeefc7a4889f528644251bdb5fc9ab5348bc2c"
registries = "General"
uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
version = "1.3.4"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "8b770b60760d4451834fe79dd483e318eee709c4"
registries = "General"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.5.2"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
version = "1.11.0"

[[deps.Profile]]
deps = ["StyledStrings"]
uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79"
version = "1.11.0"

[[deps.Random]]
deps = ["SHA"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
version = "1.11.0"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "1.0.0"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
version = "1.11.0"

[[deps.Statistics]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "e2b53ce13a53367e96601081e33d34746b571bad"
registries = "General"
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
version = "1.11.5"

    [deps.Statistics.extensions]
    SparseArraysExt = ["SparseArrays"]

    [deps.Statistics.weakdeps]
    SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.StructUtils]]
deps = ["Dates", "UUIDs"]
git-tree-sha1 = "2d0fc55c61321ba245c47be599570d11bac50303"
registries = "General"
uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42"
version = "2.8.5"

    [deps.StructUtils.extensions]
    StructUtilsMeasurementsExt = ["Measurements"]
    StructUtilsStaticArraysCoreExt = ["StaticArraysCore"]
    StructUtilsTablesExt = ["Tables"]

    [deps.StructUtils.weakdeps]
    Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
    StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
    Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[[deps.StyledStrings]]
uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
version = "1.11.0"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.3"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
version = "1.11.0"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
version = "1.11.0"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
version = "1.11.0"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.15.0+0"

[[deps.quarto_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl"]
git-tree-sha1 = "8a03ad76524ba4b6d9b073f8501756c74c2f9d39"
registries = "General"
uuid = "b7163347-bfae-5fd9-aba4-19f139889d78"
version = "1.9.38+0"

[registries.General]
url = "https://github.com/JuliaRegistries/General.git"
uuid = "23338594-aafe-5451-b93e-139f81909106"

Testing

using Test, Random

@testset "My tests" begin
    @test 0.1 + 0.2  0.3
    @test sin(randn()) ^ 2  1
    # Mark consistently failing tests as broken
    @test exp(randn()) < 0 broken=true
    # Skip flaky tests (they aren't run at all)
    @test rand() < 0.5 skip=true
end;
Test Summary: | Pass  Broken  Total  Time
My tests      |    2       2      4  0.2s

Conclusions

Further resources