# Memory allocation when creating struct with dynamic array

I am writing a package for simulating realistic quantum systems. A key component in these simulations is defining pulses. Defining several constant size structs NormPulse, SquarePulse, etc. behaves as expected. However, when I extend the abstract type with DiscretePulse which contains a dynamic array there are allocations. Here is a minimum example:

Create a project containing:

``````module Example1

using SpecialFunctions

export AbstractPulse, DiscretePulse, NormPulse, amp

abstract type AbstractPulse end

mutable struct NormPulse <: AbstractPulse
t0::Float64
t1::Float64
σ::Float64
A::Float64
B::Float64
end

mutable struct DiscretePulse <: AbstractPulse
t0::Float64
t1::Float64
res::Float64
A::Vector{ComplexF64}
N::Int
end

norm_pdf(x, μ, σ) = exp(-(x - μ)^2 / (2 * σ^2)) / (√(2pi) * σ)
norm_cdf(x, μ, σ) = (1 + erf((x - μ) / (√2 * σ))) / 2

function norm_coeff(t0, t1, σ, area)
τ = t1 - t0
a = 2norm_cdf(τ / 2, 0, σ) - 1
b = norm_pdf(0, τ / 2, σ)
area / (a - b * τ), -area / (a / b - τ)
end

function NormPulse(t0, t1, σ, area)
@assert t1 > t0
@assert σ > 0
a, b = norm_coeff(t0, t1, σ, area)
NormPulse(t0, t1, σ, a, b)
end

inbounds(p::AbstractPulse, t) = p.t0 ≤ t ≤ p.t1
function amp(p::DiscretePulse, t)::ComplexF64
if !inbounds(p, t)
return 0
end

n = min(1 + Int(floor((t - p.t0) / p.res)), p.N)
@inbounds return p.A[n]
end

end
``````

Next run:

``````activate .

using Example1

Ω1 = [NormPulse(0.0, 1+rand(), rand() / 4, pi / 2) for i = 1:5]

function test(Ω)
for i = 1:5
t1 = 1+rand()
amp(Ω[i], t1)
end
end

@btime test(Ω1)
``````

If I comment out the function amp(p::DiscretePulse, t) there are no allocations. However, if I do not, there are 2 allocations during every call of amp(Ω[i], t1). Does anyone know why this happens?

The function is called in a tight loop where allocations may dramatically impact performance.

In an important application that I cannot disclose, there is an order of magnitude reduction in performance because of this issue.

With DiscretePulse and amp(p::DiscretePulse, t) defined (not explicitly used in test):
5.882 ms (151286 allocations: 4.56 MiB)
Without:
709.623 μs (669 allocations: 35.34 KiB)

Welcome! Thanks for the MWE, but for others to help please make sure it can be run:

• `SquarePulse` is not defined
• There is an unexpected `end` in the calling code
• For the array comprehension `Ω1` you might have meant `i = 1:5` instead of `i = 5`

My inital guess for the problem is that `Ω1` is defined in the global. Try to `const Ω1 = [...` it.

1 Like

Good catch, I had quickly copied the code to create an example. I’ve since corrected it.

Why would defining `Ω1` as a const fix this issue?

When you use `const` on a variable the compiler is able to assume the type and is therefore in the position to apply optimizations. See https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables-1

Small example showing the difference:

``````julia> a = 2
2

julia> b = 3
3

julia> typeof(a)
Int64

julia> typeof(b)
Int64

julia> noconst() = a + b
noconst (generic function with 1 method)

julia> noconst()
5

julia> @code_warntype noconst()
Variables
#self#::Core.Compiler.Const(noconst, false)

Body::Any
1 ─ %1 = (Main.a + Main.b)::Any
└──      return %1

julia> const c = 2
2

julia> const d = 3
3

julia> withconst() = c + d
withconst (generic function with 1 method)

julia> withconst()
5

julia> @code_warntype withconst()
Variables
#self#::Core.Compiler.Const(withconst, false)

Body::Int64
1 ─ %1 = (Main.c + Main.d)::Core.Compiler.Const(5, false)
└──      return %1

``````

The variable is in the local scope, I meant to write @btime test(\$Ω1). Nevertheless, the issue persists.

Your MWE isn’t working on Julia 1.4.2:

``````julia> activate .
ERROR: syntax: space before "." not allowed in "activate ." at REPL:1
Stacktrace:
 top-level scope at REPL:0

julia> @btime test(Ω1)
ERROR: MethodError: no method matching amp(::NormPulse, ::Float64)
``````

So its unclear what the problem is. I’m also not sure if I understand the

The variable is in the local scope, I meant to write @btime test(\$Ω1).

comment. Do you mean that the `\$` in front of `Ω1` somehow makes it local scope? A reproducible example will certainly help…