Skip to content
AgileLogics
7 min read

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.
  • @inbounds skips 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 Int and Float64 unless you have a reason not to; mixing Float32 and Float64 silently promotes.
  • Avoid containers of abstract type (Vector{Any}, Dict{String,Any}) in hot code.
  • Strings: build large strings with an IOBuffer or join, not repeated *.
  • Check the Performance Tips chapter of the manual — it's the best single page on the topic.