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.