Benchmarking and Profiling
Prerequisites: Type stability
Measure before optimizing. Julia gives you three levels of tools.
@time and @allocated
@time runs an expression once and prints the elapsed time, the number
of allocations, and the memory allocated. The first call of a
function includes compilation, so time the second call:
# illustrative — timings differ on every machine and run
v = rand(10^6)
g(v) = sort(v; by = abs)
@time g(v)
@time g(v)
0.193596 seconds (147.31 k allocations: 23.054 MiB, 2.05% gc time, 72.86% compilation time)
0.050920 seconds (6 allocations: 15.259 MiB, 4.98% gc time)
@allocated returns just the bytes allocated, which is deterministic
and useful in tests:
squares_alloc(v) = v .^ 2
function squares_inplace!(out, v)
out .= v .^ 2
return out
end
v = rand(1000); out = similar(v)
squares_alloc(v); squares_inplace!(out, v) # compile first
println(@allocated(squares_alloc(v)) > 8000)
println(@allocated(squares_inplace!(out, v)))
true
0
The first version allocates a new 1000-element array (8000 bytes of data plus a header) on every call; the in-place version allocates nothing.
BenchmarkTools
For reliable numbers, use BenchmarkTools.jl: it runs the code many times and reports statistics.
# illustrative — timings differ on every machine and run
using BenchmarkTools
v = rand(1000)
@btime sum($v)
@btime sum(x -> x^2, $v)
@btime $v .^ 2
64.184 ns (0 allocations: 0 bytes)
68.507 ns (0 allocations: 0 bytes)
418.579 ns (3 allocations: 7.90 KiB)
@btimeprints the minimum time;@benchmarkshows a full histogram.- Interpolate global variables with
$so you measure the function, not the cost of looking up an untyped global.
Profiling
The Profile standard library samples where time is spent:
# illustrative — the report depends on your machine
using Profile
function work()
s = 0.0
for i in 1:10^7
s += sin(i) * cos(i)
end
return s
end
work() # compile first
@profile work()
Profile.print(format = :flat, sortedby = :count, mincount = 10)
In VS Code, @profview work() shows an interactive flame graph;
outside it, ProfileView.jl or PProf.jl do the same. Wide bars
are where the time goes. @profview_allocs (and
Profile.Allocs.@profile) find where memory is allocated.
A workflow
- Write clear code first, inside functions.
- Check type stability of the hot functions (
@code_warntype, JET). - Profile to find the slow part.
- Remove allocations in the hot loop (next lesson).
- Benchmark before and after each change with
@btime.