Performance Fundamentals
Julia is fast when code is written so the compiler can infer types.
Julia is fast when code is written so the compiler can infer types. The official *Performance Tips* boil down to a few rules.
23.1 Put performance-critical code in functions
Top-level code in global scope is slow; functions get compiled and specialized.
23.2 Avoid untyped global variables
Typed globals (1.8+): z::Int = 10.
x = 10 # non-constant global: type can change → slow
f() = x + 1
const Y = 10 # constant global: fast
g() = Y + 1
h(x) = x + 1 # best: pass as an argument23.3 Type stability
A function is type-stable if its return type depends only on argument types, not values.
Tools: @code_warntype, @code_typed, @code_llvm, @code_native, JET.jl (@report_opt f(x)), Cthulhu.jl (@descend).
unstable(x) = x > 0 ? x : 0 # returns Float64 or Int for Float64 input!
stable(x) = x > 0 ? x : zero(x) # always typeof(x)
@code_warntype unstable(1.5) # red `Union{Float64, Int64}` warnings
@code_warntype stable(1.5)23.4 Avoid abstract field types in structs
Also avoid containers with abstract element types (Vector{Any}, Vector{Real}) in hot code.
struct Slow
data::AbstractVector # abstract field → boxing, dynamic dispatch
end
struct Fast{T<:AbstractVector}
data::T # parametric → concrete when instantiated
end23.5 Pre-allocate and work in place
function compute!(out, x)
@inbounds for i in eachindex(out, x)
out[i] = 2x[i]^2 + 1
end
return out
end
x = rand(10^6); out = similar(x)
compute!(out, x) # zero allocations
out .= 2 .* x .^ 2 .+ 1 # fused broadcast, in place23.6 Use views for slices; loop in column-major order
function colsum(A)
s = zero(eltype(A))
for j in axes(A, 2), i in axes(A, 1) # inner loop over rows = contiguous memory
s += A[i, j]
end
s
end
@views sum(A[:, 1:10])23.7 Function barriers
function process(data) # data has an unknown (mixed) type
x = data["values"] # type unknown here
return kernel(x) # kernel is compiled for the concrete type of x
end
kernel(x::Vector{Float64}) = sum(abs2, x)23.8 Other tips
Don't change a variable's type inside a function.
Annotate keyword arguments when they influence return types, or pass types as ::Type{T} arguments.
Closures capturing reassigned variables get boxed (Core.Box) — use let blocks or avoid reassignment.
Avoid string concatenation in loops (use IOBuffer or join).
Use StaticArrays for small fixed-size vectors/matrices (2–100 elements).
Use @inbounds, @simd, @fastmath judiciously (after validating correctness).
Prefer Int/Float64 over BigInt/BigFloat unless needed.
Watch out for type piracy and invalidations that cause recompilation.