Consider the following reduced example:
using BenchmarkTools
const F = Float64
struct Species
q::F
ρ::Array{F}
end
addCharge!(dst::Array{F}, q::F, ρ::Array{F}) =
for I ∈ eachindex(dst) dst[I] += q * ρ[I] end
addCharges_bad!(dst::Array{F}, species::Vector{Species}) = begin
fill!(dst, 0)
for s ∈ species
for I ∈ eachindex(dst) dst[I] += s.q * s.ρ[I] end
end
end
addCharges_good!(dst::Array{F}, species::Vector{Species}) = begin
fill!(dst, 0)
for s ∈ species
addCharge!(dst, s.q, s.ρ)
end
end
main() = begin
a = rand(100, 100, 100)
species = [Species(1., rand(size(a)...)), Species(1., rand(size(a)...))]
@btime addCharges_bad!($a, $species)
@btime addCharges_good!($a, $species)
return
end
main()
Results:
84.034 ms (3998978 allocations: 61.02 MiB)
2.804 ms (2 allocations: 32 bytes)
I expected addCharges_bad!
not to allocate, but it does badly.
There is no reason to allocate here since no aliasing occurs.
Instead, I have to hoist the inner for loop to a function addCharge!
to perform as expected.
Is there a mechanism in Julia
allowing to mark dst
with something like @noalias
, thus ensuring no intermediate copies ? Or is there something I have missed ?