Memory Pre-allocation in the global scope

Let me try to summarize (because there is a lot of confusion around this):

  1. Your initial approach is fine in terms of performance.
    “Avoid global variables” means avoid inheriting global variables through scoping. Your code will use global variables as inputs and outputs; this is normal.
    F1!() could have been defined without taking any arguments and still work. Then it would have to obtain the values from the outer (in this case global) scope. That would be bad practice and slow unless those variables are declared to be const. The const approach is intended for actual constants that are fixed across all simulations (none of your variables). It is useful because const variables are inherently known to all functions and are still fast to use. Link

  2. You don’t need to allocate R1 and R2 in your example. Just call rand directly.
    F1!(V, P, G, i) = @. V[:,i] = rand()*(@view P[:,i]) + rand() * G

  3. You could better organize your function inputs to be more readable.

  • Do not pass array sizes as arguments. size is very cheap to compute, so passing n and N alongside the arrays themselves just makes your code harder to read and more error prone. You should write the loop (no longer needed) as for j in eachindex(G). Then you don’t need n at all: link.
  • Some consider the type declarations unnecessary fluff: link. They don’t practically do anything besides document and throw better errors. This topic often causes heated arguments though.
  • A setup function for allocating the arrays would clean up your code, but it isn’t performance necessary.
function setup(n, N)
    V = zeros(Float64, n, N)
    P = zeros(Float64, n, N)
    G = zeros(Float64, n)
    return V, P, G
end
function compute!(V, P, G, i)
    @. V[:,i] = rand()*(@view P[:,i]) + rand() * G
end

n = 10
N = 100
i = 10
V, P, G = setup(n, N)
compute!(V, P, G, i)
  • A custom type struct could further clean up your code. Though I personally think the previous version is good enough.
struct Parameters
    V::Matrix{Float64}
    P::Matrix{Float64}
    G::Vector{Float64}
end
function setup(n, N)
    V = zeros(Float64, n, N)
    P = zeros(Float64, n, N)
    G = zeros(Float64, n)
    return Parameters(V, P, G)
end
function compute!(data, i)
    @. data.V[:,i] = rand()*(@view data.P[:,i]) + rand() * data.G
end


n = 10
N = 100
i = 10
data = setup(n, N)
compute!(data, i)
  • Typically in Julia, the function and struct definitions (top half of above code) would be stored in a separate package or file from the function calls (bottom half of above code) for better organization: link. Your original code has function and variable definitions all mixed together which makes it hard to read.
4 Likes