add(x, y) = x + y
add(1, 2)3
Julia tutorial @ SC26
UCL
2026-11-16
https://juliaparallel.org/julia-hpc-tutorial-sc26/
Short form for one-line functions:
Long form for more complex functions:
-1
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]:1Type annotations in signatures are only used for dispatch, not performance. Multiple dispatch enables composability:
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"
Julia has built-in support for multi-dimensional tensors:
1×4 adjoint(::Vector{Float64}) with eltype Float64:
6.0 8.0 10.0 12.0
Performance note
Slicing makes a copy!
The LinearAlgebra stdlib exposes BLAS functionalities with a simple interface:
2×2 Matrix{Float64}:
19.0 22.0
43.0 50.0
for loops are fast, but for convenience you can use broadcasting, which enables syntactic loop fusion:
2×2 Matrix{Float64}:
3.48135 3.05936
0.500912 0.527089
@time macroJulia comes with a simple macro @time for measuring elapsed time of sufficiently long-running functions:
BenchmarkTools.jl packageFor more accurate timing of functions, packages like BenchmarkTools.jl and ChairMarks.jl provide more advanced tools:
BenchmarkTools.Trial: 10000 samples with 977 evaluations per sample. Range (min … max): 67.014 ns … 106.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.
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()
591╎ 591 @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.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:?
585╎585 @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.Julia is also compatible with third-party profilers:
LinuxPerf.jlIntelITT.jl for instrumentationNVTX.jl for instrumentationLIKWID.jl, Extrae.jl, ScoreP.jl, and more; 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 ; └ }
; 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 ; └ }
; 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" }
.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
.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
.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
General advices for improving performance of Julia code:
@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@viewusing 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)
for loopsusing 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)
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.5Virtual 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)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"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"Test Summary: | Pass Broken Total Time My tests | 2 2 4 0.2s