Memory, Allocation, and Globals
Prerequisites: Benchmarking and profiling
Once code is type-stable, the next big win is allocating less: creating fewer temporary arrays means less work for the garbage collector.
Avoid untyped globals
A non-constant global can change type at any time, so every function that uses it must check its type at run time:
scale = 2.0 # untyped global
const SCALE = 2.0 # constant
typed_scale::Float64 = 2.0 # typed global (Julia 1.8+)
f_global(xs) = sum(x * scale for x in xs)
f_const(xs) = sum(x * SCALE for x in xs)
f_typed(xs) = sum(x * typed_scale for x in xs)
f_arg(xs, s) = sum(x * s for x in xs)
xs = collect(1.0:1000.0)
for f in (f_global, f_const, f_typed)
f(xs)
end
f_arg(xs, 2.0)
println(Base.return_types(f_global, (Vector{Float64},)))
println(Base.return_types(f_const, (Vector{Float64},)))
println(Base.return_types(f_typed, (Vector{Float64},)))
println(@allocated(f_global(xs)) > 0, " ", @allocated(f_const(xs)), " ", @allocated(f_arg(xs, 2.0)))
Any[Any]
Any[Float64]
Any[Float64]
true 0 0
Best: pass values as arguments. Otherwise make globals const or
give them a type.
Preallocate and work in place
function smooth(v)
out = similar(v)
smooth!(out, v)
end
function smooth!(out, v)
n = length(v)
out[1], out[n] = v[1], v[n]
@inbounds for i in 2:n-1
out[i] = (v[i-1] + v[i] + v[i+1]) / 3
end
return out
end
v = [1.0, 4.0, 2.0, 8.0, 5.0]
println(round.(smooth(v), digits = 3))
buf = similar(v)
smooth!(buf, v); smooth!(buf, v)
println(@allocated(smooth!(buf, v)))
[1.0, 2.333, 4.667, 5.0, 5.0]
0
- A
!function that writes into a caller-provided buffer can be called in a loop without allocating. @inboundsskips bounds checks — only use it when the indexes are provably valid (here,2:n-1).
Views, fused broadcasts, and small arrays
A = rand(100, 100)
col_sum_copy(A) = sum(A[:, 1])
col_sum_view(A) = sum(@view A[:, 1])
col_sum_copy(A); col_sum_view(A)
println(@allocated(col_sum_copy(A)) > 0, " ", @allocated(col_sum_view(A)))
x = rand(1000); y = rand(1000)
axpy!(a, x, y) = (y .= a .* x .+ y; nothing)
axpy!(2.0, x, y)
println(@allocated(axpy!(2.0, x, y)))
true 0
0
For small fixed-size vectors and matrices (3D points, 4×4 transforms), StaticArrays.jl stores them on the stack, with no allocation and fully unrolled operations.
Other checklist items
- Loop over arrays in memory order (first index fastest).
- Use
IntandFloat64unless you have a reason not to; mixingFloat32andFloat64silently promotes. - Avoid containers of abstract type (
Vector{Any},Dict{String,Any}) in hot code. - Strings: build large strings with an
IOBufferorjoin, not repeated*. - Check the Performance Tips chapter of the manual — it's the best single page on the topic.