Profiling, Benchmarking & Optimization

Chairmarks.jl is a faster alternative (@b f(x)).

24.1 Timing

Chairmarks.jl is a faster alternative (@b f(x)).

@time sum(rand(10^7))           # time + allocations (first call includes compilation)
@timev f(x)                     # verbose
@elapsed f(x); @allocated f(x)
@time_imports using DataFrames  # load time per package

using BenchmarkTools
@btime sum($x)                  # $ interpolates to avoid global-variable overhead
@benchmark sort($x)             # full statistics (min, median, allocations)
b = @benchmarkable f($x) setup=(x = rand(100)); run(b)

24.2 Profiling

using Profile
@profile for _ in 1:100; f(x); end
Profile.print(format=:flat, sortedby=:count)
Profile.clear()

using ProfileView;  @profview f(x)     # flame graph (also built into the VS Code extension)
@profview_allocs f(x)                  # allocation profiler (1.8+)
using PProf; pprof()                   # Google pprof web UI

24.3 SIMD, loops & low-level optimization

Check vectorization with @code_llvm mysum(rand(100)) (look for <4 x double>).

function mysum(x)
    s = zero(eltype(x))
    @inbounds @simd for i in eachindex(x)
        s += x[i]
    end
    s
end

using LoopVectorization                 # @turbo for aggressive vectorization
@turbo for i in eachindex(x); y[i] = sin(x[i]); end

24.4 Memory & allocations

Look for unexpected allocations with @btime or --track-allocation=user.

Reuse buffers, use mul!, ldiv!, broadcasting with .=.

Immutable isbits structs live on the stack / inline in arrays.

GC.gc(), Base.summarysize(obj), varinfo().

Bumper.jl for arena allocation in hot loops.

24.5 Case study: a 100× speed-up

# Slow: global, untyped, allocating
data = rand(10^6)
function slow()
    result = []
    for x in data
        push!(result, x^2)
    end
    sum(result)
end

# Fast: argument, typed result, no temporaries
fast(data) = sum(abs2, data)
@btime fast($data)