Skip to content
AgileLogics
6 min read

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)
  • @btime prints the minimum time; @benchmark shows 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

  1. Write clear code first, inside functions.
  2. Check type stability of the hot functions (@code_warntype, JET).
  3. Profile to find the slow part.
  4. Remove allocations in the hot loop (next lesson).
  5. Benchmark before and after each change with @btime.